diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..1d972acc0467a71d16fa69636b310a04e2c52aa6 --- /dev/null +++ b/.env.example @@ -0,0 +1,89 @@ +# === Authentication === +# Admin user seeded on first run (only if no users exist in DB) +AUTH_USER=admin +AUTH_PASS=change-me-on-first-login + +# API key for headless/external access (x-api-key header) +API_KEY=generate-a-random-key + +# Primary gateway defaults (used by /api/gateways seeding if DB is empty) +MC_DEFAULT_GATEWAY_NAME=primary + +# Session cookie behavior +# - In production, cookies default to secure=true unless overridden. +# - SameSite defaults to "strict". +MC_COOKIE_SECURE= +MC_COOKIE_SAMESITE=strict + +# Network access control (middleware) +# In production, access is blocked unless the host is explicitly allowed. +# Patterns supported: +# - Exact hosts: "app.example.com" +# - Subdomains: "*.example.com" +# - Prefix wildcard: "100.*" (useful for Tailscale IPs) +MC_ALLOW_ANY_HOST= +MC_ALLOWED_HOSTS=localhost,127.0.0.1 + +# Google OAuth client IDs for Google Sign-In approval workflow +# Create in Google Cloud Console (Web application) and set authorized origins/redirects +GOOGLE_CLIENT_ID= +NEXT_PUBLIC_GOOGLE_CLIENT_ID= + +# Legacy cookie auth (backward compat, can be removed once all clients use session auth) +AUTH_SECRET=random-secret-for-legacy-cookies + +# Coordinator identity (used for coordinator chat status replies and comms UI) +MC_COORDINATOR_AGENT=coordinator +NEXT_PUBLIC_COORDINATOR_AGENT=coordinator + +# === 1Password Integration (optional) === +# Vault name for 1Password CLI pulls (used by Integrations panel) +OP_VAULT_NAME=default + +# === OpenClaw Integration === +# Path to .openclaw home directory (required for memory browser, gateway config, logs) +OPENCLAW_HOME= +# Optional: explicitly point at openclaw.json +# OPENCLAW_CONFIG_PATH= + +# Gateway connection (used by frontend WebSocket) +OPENCLAW_GATEWAY_HOST=127.0.0.1 +OPENCLAW_GATEWAY_PORT=18789 +# Optional: token used by server-side gateway calls +OPENCLAW_GATEWAY_TOKEN= + +# Frontend env vars (NEXT_PUBLIC_ prefix = available in browser) +NEXT_PUBLIC_GATEWAY_HOST= +NEXT_PUBLIC_GATEWAY_PORT=18789 +NEXT_PUBLIC_GATEWAY_PROTOCOL= +NEXT_PUBLIC_GATEWAY_URL= +# NEXT_PUBLIC_GATEWAY_TOKEN= # Optional, set if gateway requires auth token + +# === Data Paths (all optional, defaults to .data/ in project root) === +# MISSION_CONTROL_DATA_DIR=.data +# MISSION_CONTROL_DB_PATH=.data/mission-control.db +# MISSION_CONTROL_TOKENS_PATH=.data/mission-control-tokens.json + +# === OpenClaw Paths (derived from OPENCLAW_HOME if not set) === +# OPENCLAW_LOG_DIR=/home/openclaw/.openclaw/logs +# OPENCLAW_MEMORY_DIR=/home/openclaw/.openclaw/memory +# OPENCLAW_SOUL_TEMPLATES_DIR=/home/openclaw/.openclaw/templates/souls +# OPENCLAW_BIN=openclaw + +# === Super Admin / Provisioning (optional) === +# Path to this repo root, needed if you use the super-admin provisioning helpers. +# MISSION_CONTROL_REPO_ROOT=/path/to/mission-control +# Template openclaw.json used to seed new tenant state (required for tenant bootstrap). +# MC_SUPER_TEMPLATE_OPENCLAW_JSON=/path/to/openclaw.json +# Base path used for provisioned linux user homes (default: /home) +# MC_TENANT_HOME_ROOT=/home +# Workspace directory name under each tenant user home (default: workspace) +# MC_TENANT_WORKSPACE_DIRNAME=workspace + +# === Data Retention (days, 0 = keep forever) === +# MC_RETAIN_ACTIVITIES_DAYS=90 +# MC_RETAIN_AUDIT_DAYS=365 +# MC_RETAIN_LOGS_DAYS=30 +# MC_RETAIN_NOTIFICATIONS_DAYS=60 +# MC_RETAIN_PIPELINE_RUNS_DAYS=90 +# MC_RETAIN_TOKEN_USAGE_DAYS=90 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000000000000000000000000000000000..1691f22a315762e154b4e48ff0b620d47d2e4003 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.png filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000000000000000000000000000000000..e90db18fe91410c5b23dd0f21fb766a40e06b2c6 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ +# Summary +Describe what changed and why. + +# Risk Level +Low / Medium / High (pick one) + +# Tests +List commands run and results. + +# Contribution Checklist +- [ ] Tests added/updated for behavior changes +- [ ] Lint/typecheck/build passing +- [ ] Security review done if auth/data/crypto touched +- [ ] DB migration tested if schema changed + +# Notes +Anything reviewers should know. diff --git a/.github/workflows/quality-gate.yml b/.github/workflows/quality-gate.yml new file mode 100644 index 0000000000000000000000000000000000000000..b1187ffc4dde2e379f688657aaf133818c183519 --- /dev/null +++ b/.github/workflows/quality-gate.yml @@ -0,0 +1,49 @@ +name: Quality Gate + +on: + pull_request: + push: + branches: [main] + +concurrency: + group: quality-gate-${{ github.ref }} + cancel-in-progress: true + +jobs: + quality-gate: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10 + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps + + - name: Lint + run: pnpm lint + + - name: Typecheck + run: pnpm typecheck + + - name: Unit tests + run: pnpm test + + - name: E2E tests + run: pnpm test:e2e + + - name: Build + run: pnpm build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..06fd45d86c3e23a9aa4420e5f00022c58f76d131 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# Dependencies +node_modules/ + +# Next.js +.next/ +out/ + +# Production +build/ +dist/ + +# Misc +.DS_Store +*.pem +.env*.local + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Vercel +.vercel + +# TypeScript +*.tsbuildinfo +next-env.d.ts +.env +.data/ +aegis/ + +# Claude Code context files +CLAUDE.md +**/CLAUDE.md diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000000000000000000000000000000000..c13aad76134c1d8b5b81c9de363e0a74aa9729c6 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +onlyBuiltDependenciesFile= +ignore-scripts=false diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000000000000000000000000000000000..3e6c137fb90646f27aefb8826ffc45137abde1b0 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,64 @@ +# Contributing to Mission Control + +Thank you for your interest in contributing to Mission Control. + +## Getting Started + +```bash +# Clone the repo +git clone https://github.com/builderz-labs/mission-control.git +cd mission-control + +# Install dependencies +pnpm install + +# Copy environment config +cp .env.example .env +# Edit .env with your values + +# Start development server +pnpm dev +``` + +## Development Workflow + +1. Fork the repository and create a feature branch from `main`. +2. Make your changes — keep commits focused and descriptive. +3. Run the quality gate before submitting: + ```bash + pnpm quality:gate # lint + typecheck + test + e2e + build + ``` +4. Open a pull request against `main` using the PR template. + +## Code Style + +- TypeScript strict mode — no `any` unless absolutely necessary. +- Tailwind CSS for styling — use semantic design tokens (`text-foreground`, `bg-card`, etc.). +- Server components by default; `'use client'` only when needed. +- API routes use `requireRole()` for auth and return JSON responses. + +## Project Structure + +- `src/app/api/` — Next.js API routes (REST endpoints) +- `src/components/panels/` — Feature panels rendered by the SPA shell +- `src/components/layout/` — Navigation, header, and layout components +- `src/lib/` — Shared utilities (auth, database, config, scheduler) +- `src/store/` — Zustand state management + +## Testing + +- **Unit tests**: Vitest — `pnpm test` +- **E2E tests**: Playwright — `pnpm test:e2e` +- **Type checking**: `pnpm typecheck` +- **Lint**: `pnpm lint` + +## Reporting Bugs + +Open an issue with: +- Steps to reproduce +- Expected vs actual behavior +- Browser/OS/Node version + +## License + +By contributing, you agree that your contributions will be licensed under the [MIT License](LICENSE). diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8bd4a619e7e36584314ee875ed7830867848c6ab --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Builderz Labs + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000000000000000000000000000000000000..823c9be3a8ba8c318947af54ae55ac43872ef4b5 --- /dev/null +++ b/README.md @@ -0,0 +1,293 @@ +
+ +# Mission Control + +**The open-source dashboard for AI agent orchestration.** + +Manage agent fleets, track tasks, monitor costs, and orchestrate workflows — all from a single pane of glass. + +[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE) +[![Next.js 16](https://img.shields.io/badge/Next.js-16-black?logo=next.js)](https://nextjs.org/) +[![React 19](https://img.shields.io/badge/React-19-61DAFB?logo=react&logoColor=white)](https://react.dev/) +[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-3178C6?logo=typescript&logoColor=white)](https://typescriptlang.org/) +[![SQLite](https://img.shields.io/badge/SQLite-WAL-003B57?logo=sqlite&logoColor=white)](https://sqlite.org/) + +
+ +--- + +## Why Mission Control? + +Running AI agents at scale means juggling sessions, tasks, costs, and reliability across multiple models and channels. Mission Control gives you: + +- **20+ panels** — Tasks, agents, logs, tokens, memory, cron, alerts, webhooks, pipelines, and more +- **Real-time everything** — WebSocket + SSE push updates, smart polling that pauses when you're away +- **Zero external dependencies** — SQLite database, single `pnpm start` to run, no Redis/Postgres/Docker required +- **Role-based access** — Viewer, operator, and admin roles with session + API key auth +- **Quality gates** — Built-in review system that blocks task completion without sign-off +- **Multi-gateway** — Connect to multiple OpenClaw gateways simultaneously + +## Quick Start + +```bash +git clone https://github.com/builderz-labs/mission-control.git +cd mission-control +pnpm install +cp .env.example .env # edit with your values +pnpm dev # http://localhost:3000 +``` + +Initial login is seeded from `AUTH_USER` / `AUTH_PASS` on first run. + +## Features + +### Agent Management +Monitor agent status, spawn new sessions, view heartbeats, and manage the full agent lifecycle from registration to retirement. + +### Task Board +Kanban board with six columns (inbox → backlog → todo → in-progress → review → done), drag-and-drop, priority levels, assignments, and threaded comments. + +### Real-time Monitoring +Live activity feed, session inspector, and log viewer with filtering. WebSocket connection to OpenClaw gateway for instant event delivery. + +### Cost Tracking +Token usage dashboard with per-model breakdowns, trend charts, and cost analysis powered by Recharts. + +### Background Automation +Scheduled tasks for database backups, stale record cleanup, and agent heartbeat monitoring. Configurable via UI or API. + +### Integrations +Outbound webhooks with delivery history, configurable alert rules with cooldowns, and multi-gateway connection management. Optional 1Password CLI integration for secret management. + +## Architecture + +``` +mission-control/ +├── middleware.ts # Auth gate + network access control +├── src/ +│ ├── app/ +│ │ ├── page.tsx # SPA shell — routes all panels +│ │ ├── login/page.tsx # Login page +│ │ └── api/ # 25+ REST API routes +│ ├── components/ +│ │ ├── layout/ # NavRail, HeaderBar, LiveFeed +│ │ ├── dashboard/ # Overview dashboard +│ │ ├── panels/ # 23 feature panels +│ │ └── chat/ # Agent chat UI +│ ├── lib/ +│ │ ├── auth.ts # Session + API key auth, RBAC +│ │ ├── db.ts # SQLite (better-sqlite3, WAL mode) +│ │ ├── migrations.ts # 11 schema migrations +│ │ ├── scheduler.ts # Background task scheduler +│ │ ├── webhooks.ts # Outbound webhook delivery +│ │ └── websocket.ts # Gateway WebSocket client +│ └── store/index.ts # Zustand state management +└── .data/ # Runtime data (SQLite DB, token logs) +``` + +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| Framework | Next.js 16 (App Router) | +| UI | React 19, Tailwind CSS 3.4 | +| Language | TypeScript 5.7 | +| Database | SQLite via better-sqlite3 (WAL mode) | +| State | Zustand 5 | +| Charts | Recharts 3 | +| Real-time | WebSocket + Server-Sent Events | +| Auth | scrypt hashing, session tokens, RBAC | +| Testing | Vitest + Playwright | + +## Authentication + +Three auth methods, three roles: + +| Method | Details | +|--------|---------| +| Session cookie | `POST /api/auth/login` sets `mc-session` (7-day expiry) | +| API key | `x-api-key` header matches `API_KEY` env var | +| Google Sign-In | OAuth with admin approval workflow | + +| Role | Access | +|------|--------| +| `viewer` | Read-only | +| `operator` | Read + write (tasks, agents, chat) | +| `admin` | Full access (users, settings, system ops) | + +## API Reference + +All endpoints require authentication unless noted. Full reference below. + +
+Auth + +| Method | Path | Description | +|--------|------|-------------| +| `POST` | `/api/auth/login` | Login with username/password | +| `POST` | `/api/auth/google` | Google Sign-In | +| `POST` | `/api/auth/logout` | Destroy session | +| `GET` | `/api/auth/me` | Current user info | +| `GET` | `/api/auth/access-requests` | List pending access requests (admin) | +| `POST` | `/api/auth/access-requests` | Approve/reject requests (admin) | + +
+ +
+Core Resources + +| Method | Path | Role | Description | +|--------|------|------|-------------| +| `GET` | `/api/agents` | viewer | List agents with task stats | +| `POST` | `/api/agents` | operator | Register/update agent | +| `GET` | `/api/tasks` | viewer | List tasks (filter: `?status=`, `?assigned_to=`, `?priority=`) | +| `POST` | `/api/tasks` | operator | Create task | +| `GET` | `/api/tasks/[id]` | viewer | Task details | +| `PUT` | `/api/tasks/[id]` | operator | Update task | +| `DELETE` | `/api/tasks/[id]` | admin | Delete task | +| `GET` | `/api/tasks/[id]/comments` | viewer | Task comments | +| `POST` | `/api/tasks/[id]/comments` | operator | Add comment | +| `POST` | `/api/tasks/[id]/broadcast` | operator | Broadcast task to agents | + +
+ +
+Monitoring + +| Method | Path | Role | Description | +|--------|------|------|-------------| +| `GET` | `/api/status` | viewer | System status (uptime, memory, disk) | +| `GET` | `/api/activities` | viewer | Activity feed | +| `GET` | `/api/notifications` | viewer | Notifications for recipient | +| `GET` | `/api/sessions` | viewer | Active gateway sessions | +| `GET` | `/api/tokens` | viewer | Token usage and cost data | +| `GET` | `/api/standup` | viewer | Standup report history | +| `POST` | `/api/standup` | operator | Generate standup | + +
+ +
+Configuration + +| Method | Path | Role | Description | +|--------|------|------|-------------| +| `GET/PUT` | `/api/settings` | admin | App settings | +| `GET/PUT` | `/api/gateway-config` | admin | OpenClaw gateway config | +| `GET/POST` | `/api/cron` | admin | Cron management | + +
+ +
+Operations + +| Method | Path | Role | Description | +|--------|------|------|-------------| +| `GET/POST` | `/api/scheduler` | admin | Background task scheduler | +| `GET` | `/api/audit` | admin | Audit log | +| `GET` | `/api/logs` | viewer | Agent log browser | +| `GET` | `/api/memory` | viewer | Memory file browser/search | +| `GET` | `/api/search` | viewer | Global search | +| `GET` | `/api/export` | admin | CSV export | + +
+ +
+Integrations + +| Method | Path | Role | Description | +|--------|------|------|-------------| +| `GET/POST/PUT/DELETE` | `/api/webhooks` | admin | Webhook CRUD | +| `POST` | `/api/webhooks/test` | admin | Test delivery | +| `GET` | `/api/webhooks/deliveries` | admin | Delivery history | +| `GET/POST/PUT/DELETE` | `/api/alerts` | admin | Alert rules | +| `GET/POST/PUT/DELETE` | `/api/gateways` | admin | Gateway connections | +| `GET/PUT/DELETE/POST` | `/api/integrations` | admin | Integration management | + +
+ +
+Chat & Real-time + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/api/events` | SSE stream of DB changes | +| `GET/POST` | `/api/chat/conversations` | Conversation CRUD | +| `GET/POST` | `/api/chat/messages` | Message CRUD | + +
+ +
+Agent Lifecycle + +| Method | Path | Role | Description | +|--------|------|------|-------------| +| `POST` | `/api/spawn` | operator | Spawn agent session | +| `POST` | `/api/agents/[id]/heartbeat` | operator | Agent heartbeat | +| `POST` | `/api/agents/[id]/wake` | operator | Wake sleeping agent | +| `POST` | `/api/quality-review` | operator | Submit quality review | + +
+ +
+Pipelines + +| Method | Path | Role | Description | +|--------|------|------|-------------| +| `GET` | `/api/pipelines` | viewer | List pipeline runs | +| `POST` | `/api/pipelines/run` | operator | Start pipeline | +| `GET/POST` | `/api/workflows` | viewer/admin | Workflow templates | + +
+ +## Environment Variables + +See [`.env.example`](.env.example) for the complete list. Key variables: + +| Variable | Required | Description | +|----------|----------|-------------| +| `AUTH_USER` | No | Initial admin username (default: `admin`) | +| `AUTH_PASS` | No | Initial admin password | +| `API_KEY` | No | API key for headless access | +| `OPENCLAW_HOME` | Yes* | Path to `.openclaw` directory | +| `OPENCLAW_GATEWAY_HOST` | No | Gateway host (default: `127.0.0.1`) | +| `OPENCLAW_GATEWAY_PORT` | No | Gateway WebSocket port (default: `18789`) | +| `MC_ALLOWED_HOSTS` | No | Host allowlist for production | + +*Memory browser, log viewer, and gateway config require `OPENCLAW_HOME`. + +## Deployment + +```bash +# Build +pnpm install --frozen-lockfile +pnpm build + +# Run +OPENCLAW_HOME=/path/to/.openclaw pnpm start +``` + +Network access is restricted by default in production. Set `MC_ALLOWED_HOSTS` (comma-separated) or `MC_ALLOW_ANY_HOST=1` to control access. + +## Development + +```bash +pnpm dev # Dev server +pnpm build # Production build +pnpm typecheck # TypeScript check +pnpm lint # ESLint +pnpm test # Vitest unit tests +pnpm test:e2e # Playwright E2E +pnpm quality:gate # All checks +``` + +## Contributing + +Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup instructions and guidelines. + +## Security + +To report a vulnerability, see [SECURITY.md](SECURITY.md). + +## License + +[MIT](LICENSE) © 2026 [Builderz Labs](https://github.com/builderz-labs) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000000000000000000000000000000..75c8ce325907a98ae49659d4f5922a4981df9159 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,31 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in Mission Control, please report it responsibly. + +**Do not open a public issue.** Instead, email security@builderz.dev with: + +- Description of the vulnerability +- Steps to reproduce +- Potential impact +- Suggested fix (if any) + +We will acknowledge receipt within 48 hours and aim to provide a fix or mitigation within 7 days for critical issues. + +## Supported Versions + +| Version | Supported | +|---------|-----------| +| latest `main` | Yes | +| older releases | Best effort | + +## Security Considerations + +Mission Control handles authentication credentials and API keys. When deploying: + +- Always set strong values for `AUTH_PASS` and `API_KEY`. +- Use `MC_ALLOWED_HOSTS` to restrict network access in production. +- Keep `.env` files out of version control (already in `.gitignore`). +- Enable `MC_COOKIE_SECURE=true` when serving over HTTPS. +- Review the [Environment Variables](README.md#environment-variables) section for all security-relevant configuration. diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000000000000000000000000000000000000..fc939510803df69da1dd059f5af318df3f215c58 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,23 @@ +import next from 'eslint-config-next' + +const config = [ + ...next, + { + ignores: [ + '.data/**', + 'ops/**', + ], + }, + // The React 19/ESLint ecosystem is still settling. These rules are valuable, + // but they currently trigger a lot of false positives in this codebase. + // Keep them off until we do a dedicated refactor pass. + { + rules: { + 'react-hooks/set-state-in-effect': 'off', + 'react-hooks/purity': 'off', + 'react-hooks/immutability': 'off', + }, + }, +] + +export default config diff --git a/middleware.ts b/middleware.ts new file mode 100644 index 0000000000000000000000000000000000000000..f30dffd13b1f7c2eebe51429c39c4f77db5f93f9 --- /dev/null +++ b/middleware.ts @@ -0,0 +1,100 @@ +import { NextResponse } from 'next/server' +import type { NextRequest } from 'next/server' + +function envFlag(name: string): boolean { + const raw = process.env[name] + if (raw === undefined) return false + const v = String(raw).trim().toLowerCase() + return v === '1' || v === 'true' || v === 'yes' || v === 'on' +} + +function getRequestHostname(request: NextRequest): string { + const raw = request.headers.get('x-forwarded-host') || request.headers.get('host') || '' + // If multiple hosts are present, take the first (proxy chain). + const first = raw.split(',')[0] || '' + return first.trim().split(':')[0] || '' +} + +function hostMatches(pattern: string, hostname: string): boolean { + const p = pattern.trim().toLowerCase() + const h = hostname.trim().toLowerCase() + if (!p || !h) return false + + // "*.example.com" matches "a.example.com" (but not bare "example.com") + if (p.startsWith('*.')) { + const suffix = p.slice(2) + return h.endsWith(`.${suffix}`) + } + + // "100.*" matches "100.64.0.1" + if (p.endsWith('.*')) { + const prefix = p.slice(0, -1) + return h.startsWith(prefix) + } + + return h === p +} + +export function middleware(request: NextRequest) { + // Network access control. + // In production: default-deny unless explicitly allowed. + // In dev/test: allow all hosts unless overridden. + const hostName = getRequestHostname(request) + const allowAnyHost = envFlag('MC_ALLOW_ANY_HOST') || process.env.NODE_ENV !== 'production' + const allowedPatterns = String(process.env.MC_ALLOWED_HOSTS || '') + .split(',') + .map((s) => s.trim()) + .filter(Boolean) + + const isAllowedHost = allowAnyHost || allowedPatterns.some((p) => hostMatches(p, hostName)) + + if (!isAllowedHost) { + return new NextResponse('Forbidden', { status: 403 }) + } + + const { pathname } = request.nextUrl + + // Allow login page and auth API without session + if (pathname === '/login' || pathname.startsWith('/api/auth/')) { + return NextResponse.next() + } + + // Check for session cookie + const sessionToken = request.cookies.get('mc-session')?.value + + // API routes: accept session cookie OR API key + if (pathname.startsWith('/api/')) { + const apiKey = request.headers.get('x-api-key') + if (sessionToken || (apiKey && apiKey === process.env.API_KEY)) { + return NextResponse.next() + } + + // Backward compat: accept legacy cookie during migration + const legacyCookie = request.cookies.get('mission-control-auth') + if (legacyCookie?.value === process.env.AUTH_SECRET) { + return NextResponse.next() + } + + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + // Page routes: redirect to login if no session + if (sessionToken) { + return NextResponse.next() + } + + // Backward compat: accept legacy cookie + const legacyCookie = request.cookies.get('mission-control-auth') + if (legacyCookie?.value === process.env.AUTH_SECRET) { + return NextResponse.next() + } + + // Redirect to login + const loginUrl = request.nextUrl.clone() + loginUrl.pathname = '/login' + return NextResponse.redirect(loginUrl) +} + +export const config = { + matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'] +} diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000000000000000000000000000000000000..baddbebe03296ec645fde249f256ab4460382a62 --- /dev/null +++ b/next.config.js @@ -0,0 +1,46 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + turbopack: {}, + + // Security headers + async headers() { + const googleEnabled = !!(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || process.env.GOOGLE_CLIENT_ID) + + const csp = [ + `default-src 'self'`, + `script-src 'self' 'unsafe-inline' 'unsafe-eval'${googleEnabled ? ' https://accounts.google.com' : ''}`, + `style-src 'self' 'unsafe-inline'`, + `connect-src 'self' ws: wss: http://127.0.0.1:* http://localhost:*`, + `img-src 'self' data: blob:${googleEnabled ? ' https://*.googleusercontent.com https://lh3.googleusercontent.com' : ''}`, + `font-src 'self' data:`, + `frame-src 'self'${googleEnabled ? ' https://accounts.google.com' : ''}`, + ].join('; ') + + return [ + { + source: '/:path*', + headers: [ + { key: 'X-Frame-Options', value: 'DENY' }, + { key: 'X-Content-Type-Options', value: 'nosniff' }, + { key: 'X-XSS-Protection', value: '1; mode=block' }, + { key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' }, + { key: 'Content-Security-Policy', value: csp }, + { key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' }, + ], + }, + ]; + }, + + webpack: (config) => { + config.resolve.fallback = { + ...config.resolve.fallback, + net: false, + os: false, + fs: false, + path: false, + }; + return config; + }, +}; + +module.exports = nextConfig; diff --git a/ops/mc-provisioner-daemon.js b/ops/mc-provisioner-daemon.js new file mode 100644 index 0000000000000000000000000000000000000000..a5fb0bfff742abee6b6920febb28eac07f656b6d --- /dev/null +++ b/ops/mc-provisioner-daemon.js @@ -0,0 +1,302 @@ +#!/usr/bin/env node + +const fs = require('fs') +const net = require('net') +const { spawn } = require('child_process') +const path = require('path') + +const SOCKET_PATH = process.env.MC_PROVISIONER_SOCKET || '/run/mc-provisioner.sock' +const TOKEN = String(process.env.MC_PROVISIONER_TOKEN || '') +const SOCKET_GROUP = process.env.MC_PROVISIONER_GROUP || 'openclaw' +const REPO_ROOT = process.env.MISSION_CONTROL_REPO_ROOT || path.resolve(__dirname, '..') +const DATA_DIR = process.env.MISSION_CONTROL_DATA_DIR || path.join(REPO_ROOT, '.data') +const TENANT_HOME_ROOT = String(process.env.MC_TENANT_HOME_ROOT || '/home').trim() || '/home' +const TENANT_WORKSPACE_DIRNAME = String(process.env.MC_TENANT_WORKSPACE_DIRNAME || 'workspace').trim() || 'workspace' +const TEMPLATE_OPENCLAW_JSON = process.env.MC_SUPER_TEMPLATE_OPENCLAW_JSON || (process.env.OPENCLAW_HOME ? path.join(process.env.OPENCLAW_HOME, 'openclaw.json') : '') +const GATEWAY_SYSTEMD_TEMPLATE = path.join(REPO_ROOT, 'ops', 'templates', 'openclaw-gateway@.service') + +if (!TOKEN) { + console.error('MC_PROVISIONER_TOKEN is required') + process.exit(1) +} + +function escapeRegExp(str) { + return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&') +} + +function isSafeUser(user) { + return /^[a-z_][a-z0-9_-]{1,30}$/.test(user) +} + +function pathJoinPosix(...parts) { + // Use posix paths for allowlisting because provisioner executes linux commands. + const cleaned = parts.map((p) => String(p || '').replace(/\/+$/g, '')) + return path.posix.join(...cleaned) +} + +function isSafeHomePath(path, user, suffix) { + return path === pathJoinPosix(TENANT_HOME_ROOT, user, suffix) +} + +function validateCommand(command, args) { + const cmd = String(command || '').split('/').pop() + if (!command || !Array.isArray(args)) return 'Invalid command payload' + + if (cmd === 'useradd') { + if (args.length !== 4) return 'useradd argument mismatch' + const [a, b, shell, user] = args + if (a !== '-m' || b !== '-s' || shell !== '/bin/bash') return 'useradd args not allowed' + if (!isSafeUser(user)) return 'Invalid username' + return null + } + + if (cmd === 'install') { + if (args.length !== 8) return 'install argument mismatch' + const [d, mFlag, mode, oFlag, userA, gFlag, userB, target] = args + if (d !== '-d' || mFlag !== '-m' || oFlag !== '-o' || gFlag !== '-g') return 'install args not allowed' + if (!['0750', '0700'].includes(mode)) return 'install mode not allowed' + const isRootOwned = userA === 'root' && userB === 'root' + const isTenantOwned = isSafeUser(userA) && isSafeUser(userB) && userA === userB + if (!isRootOwned && !isTenantOwned) return 'install ownership not allowed' + const openclawPath = pathJoinPosix(TENANT_HOME_ROOT, userA, '.openclaw') + const workspacePath = pathJoinPosix(TENANT_HOME_ROOT, userA, TENANT_WORKSPACE_DIRNAME) + if (isRootOwned && target === '/etc/openclaw-tenants') return null + if (![openclawPath, workspacePath].includes(target)) return 'install path not allowed' + return null + } + + if (cmd === 'cp') { + if (args.length !== 3) return 'cp argument mismatch' + const [flag, source, target] = args + if (!['-n', '-f'].includes(flag)) return 'cp flag not allowed' + if (TEMPLATE_OPENCLAW_JSON && source === TEMPLATE_OPENCLAW_JSON) { + if (flag !== '-n') return 'openclaw config copy must use -n' + const homeRootRe = escapeRegExp(pathJoinPosix(TENANT_HOME_ROOT)) + const match = new RegExp(`^${homeRootRe}\\/([a-z_][a-z0-9_-]{1,30})\\/\\.openclaw\\/openclaw\\.json$`).exec(target) + if (!match) return 'cp target not allowed' + return null + } + if (source === GATEWAY_SYSTEMD_TEMPLATE) { + if (flag !== '-n') return 'template copy must use -n' + if (target !== '/etc/systemd/system/openclaw-gateway@.service') return 'gateway template target not allowed' + return null + } + const provisionerEnvRe = new RegExp(`^${escapeRegExp(path.join(DATA_DIR, 'provisioner'))}\\/([a-z0-9-]{3,32})\\/openclaw-gateway\\.env$`) + if (provisionerEnvRe.test(source)) { + if (flag !== '-f') return 'tenant env copy must use -f' + if (!/^\/etc\/openclaw-tenants\/[a-z_][a-z0-9_-]{1,30}\.env$/.test(target)) return 'tenant env target not allowed' + return null + } + return 'cp source not allowed' + } + + if (cmd === 'chown') { + if (args.length !== 3) return 'chown argument mismatch' + const [rFlag, owner, target] = args + if (rFlag !== '-R') return 'chown must use -R' + const [userA, userB] = owner.split(':') + if (!isSafeUser(userA) || userA !== userB) return 'chown owner not allowed' + if (target !== pathJoinPosix(TENANT_HOME_ROOT, userA)) return 'chown target not allowed' + return null + } + + if (cmd === 'rm') { + if (args.length !== 2) return 'rm argument mismatch' + const [flag, target] = args + + if (flag === '-f') { + if (!/^\/etc\/openclaw-tenants\/[a-z_][a-z0-9_-]{1,30}\.env$/.test(target)) { + return 'rm -f target not allowed' + } + return null + } + + if (flag === '-rf') { + const homeRootRe = escapeRegExp(pathJoinPosix(TENANT_HOME_ROOT)) + const ws = escapeRegExp(TENANT_WORKSPACE_DIRNAME) + const match = new RegExp(`^${homeRootRe}\\/([a-z_][a-z0-9_-]{1,30})\\/(\\.openclaw|${ws})$`).exec(target) + if (!match) return 'rm -rf target not allowed' + return null + } + + return 'rm flag not allowed' + } + + if (cmd === 'userdel') { + if (args.length !== 2) return 'userdel argument mismatch' + if (args[0] !== '-r') return 'userdel must use -r' + if (!isSafeUser(args[1])) return 'Invalid username' + return null + } + + if (cmd === 'true') { + if (args.length !== 0) return 'true takes no args' + return null + } + + if (cmd === 'systemctl') { + if (args.length === 1 && args[0] === 'daemon-reload') return null + if (args.length === 3 && args[0] === 'enable' && args[1] === '--now') { + if (/^openclaw-gateway@[a-z_][a-z0-9_-]{1,30}\.service$/.test(args[2])) return null + return 'systemctl service name not allowed' + } + if (args.length === 3 && args[0] === 'disable' && args[1] === '--now') { + if (/^openclaw-gateway@[a-z_][a-z0-9_-]{1,30}\.service$/.test(args[2])) return null + return 'systemctl service name not allowed' + } + return 'systemctl args not allowed' + } + + return `Command not allowlisted: ${command}` +} + +function run(command, args, timeoutMs) { + return new Promise((resolve) => { + const child = spawn(command, args, { shell: false }) + let stdout = '' + let stderr = '' + let timedOut = false + + const timer = setTimeout(() => { + timedOut = true + child.kill('SIGKILL') + }, Math.max(1000, Number(timeoutMs || 10000))) + + child.stdout.on('data', (d) => { stdout += d.toString('utf8') }) + child.stderr.on('data', (d) => { stderr += d.toString('utf8') }) + + child.on('close', (code) => { + clearTimeout(timer) + resolve({ + ok: !timedOut && code === 0, + code: timedOut ? 124 : code, + stdout, + stderr: timedOut ? `${stderr}\nTimed out` : stderr, + }) + }) + + child.on('error', (err) => { + clearTimeout(timer) + resolve({ ok: false, code: 1, stdout, stderr: `${stderr}\n${err.message}` }) + }) + }) +} + +function sleep(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)) +} + +async function runWithRetry(command, args, timeoutMs) { + const cmd = String(command || '').split('/').pop() + const maxAttempts = cmd === 'useradd' ? 6 : 1 + let last = null + + for (let attempt = 1; attempt <= maxAttempts; attempt += 1) { + const result = await run(command, args, timeoutMs) + last = result + if (result.ok) return result + + const transientLock = + cmd === 'useradd' && + /cannot lock \/etc\/passwd/i.test(String(result.stderr || '')) + + if (!transientLock || attempt === maxAttempts) { + return result + } + await sleep(800) + } + + return last || { ok: false, code: 1, stdout: '', stderr: 'Unknown execution failure' } +} + +function writeResp(socket, obj) { + try { + socket.write(JSON.stringify(obj) + '\n') + } catch { + // no-op + } finally { + socket.end() + } +} + +if (fs.existsSync(SOCKET_PATH)) { + try { + fs.unlinkSync(SOCKET_PATH) + } catch (err) { + console.error(`Failed to remove stale socket ${SOCKET_PATH}:`, err.message) + process.exit(1) + } +} + +const server = net.createServer((socket) => { + let buf = '' + + socket.on('data', async (chunk) => { + buf += chunk.toString('utf8') + const idx = buf.indexOf('\n') + if (idx === -1) return + + const line = buf.slice(0, idx) + buf = buf.slice(idx + 1) + + let req + try { + req = JSON.parse(line) + } catch { + writeResp(socket, { ok: false, error: 'Invalid JSON' }) + return + } + + if (!req || req.token !== TOKEN) { + writeResp(socket, { ok: false, error: 'Unauthorized' }) + return + } + + const command = String(req.command || '') + const args = Array.isArray(req.args) ? req.args.map((a) => String(a)) : [] + const dryRun = !!req.dryRun + const timeoutMs = Number(req.timeoutMs || 10000) + + const validationErr = validateCommand(command, args) + if (validationErr) { + writeResp(socket, { ok: false, error: validationErr }) + return + } + + if (dryRun) { + writeResp(socket, { ok: true, code: 0, stdout: '', stderr: '', skipped: true }) + return + } + + const result = await runWithRetry(command, args, timeoutMs) + if (!result.ok) { + writeResp(socket, { ok: false, code: result.code, stdout: result.stdout, stderr: result.stderr, error: `Command failed: ${command}` }) + return + } + + writeResp(socket, { ok: true, code: result.code, stdout: result.stdout, stderr: result.stderr, skipped: false }) + }) +}) + +server.listen(SOCKET_PATH, () => { + fs.chmodSync(SOCKET_PATH, 0o660) + try { + const group = require('child_process').execSync(`getent group ${SOCKET_GROUP} | cut -d: -f3`).toString('utf8').trim() + const gid = Number(group) + if (Number.isInteger(gid)) { + fs.chownSync(SOCKET_PATH, 0, gid) + } + } catch { + // fallback: keep root:root + } + console.log(`mc-provisioner listening on ${SOCKET_PATH}`) +}) + +function shutdown() { + try { server.close() } catch {} + try { fs.unlinkSync(SOCKET_PATH) } catch {} + process.exit(0) +} + +process.on('SIGINT', shutdown) +process.on('SIGTERM', shutdown) diff --git a/ops/templates/openclaw-gateway@.service b/ops/templates/openclaw-gateway@.service new file mode 100644 index 0000000000000000000000000000000000000000..6c327e18a102b15f987b1eacb6657f51687c8edb --- /dev/null +++ b/ops/templates/openclaw-gateway@.service @@ -0,0 +1,23 @@ +[Unit] +Description=OpenClaw Gateway (%i) +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=%i +Group=%i +SupplementaryGroups=docker +WorkingDirectory=/home/%i +Environment=HOME=/home/%i +Environment=PATH=/home/%i/bin:/usr/local/bin:/usr/bin:/bin:/home/%i/.local/bin +Environment=NODE_OPTIONS=--experimental-sqlite +EnvironmentFile=-/etc/openclaw-tenants/%i.env +ExecStart=/usr/local/bin/openclaw gateway --port ${OPENCLAW_GATEWAY_PORT} +Restart=always +RestartSec=5 +NoNewPrivileges=true +LimitNOFILE=65535 + +[Install] +WantedBy=multi-user.target diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000000000000000000000000000000000000..26fe36d90d3ad1f01bc7ca9bf7e0472d632ff013 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,9932 @@ +{ + "name": "mission-control", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "mission-control", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "@xyflow/react": "^12.10.0", + "autoprefixer": "^10.4.20", + "better-sqlite3": "^12.6.2", + "clsx": "^2.1.1", + "eslint": "^9.18.0", + "eslint-config-next": "^16.1.6", + "next": "^16.1.6", + "next-themes": "^0.4.6", + "postcss": "^8.5.2", + "react": "^19.0.1", + "react-dom": "^19.0.1", + "reactflow": "^11.11.4", + "recharts": "^3.7.0", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2", + "ws": "^8.19.0", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@playwright/test": "^1.51.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@types/better-sqlite3": "^7.6.13", + "@types/node": "^22.10.6", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@types/ws": "^8.18.1", + "@vitejs/plugin-react": "^4.3.4", + "jsdom": "^26.0.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^2.1.5" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz", + "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.3", + "@csstools/css-color-parser": "^3.0.9", + "@csstools/css-parser-algorithms": "^3.0.4", + "@csstools/css-tokenizer": "^3.0.3", + "lru-cache": "^10.4.3" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "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==", + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "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/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.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.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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==", + "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/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "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==", + "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==", + "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/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "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==", + "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==", + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "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==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@next/env": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "license": "MIT" + }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.1.6.tgz", + "integrity": "sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==", + "license": "MIT", + "dependencies": { + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@playwright/test": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", + "integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@reactflow/background": { + "version": "11.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/background/-/background-11.3.14.tgz", + "integrity": "sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/background/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/controls": { + "version": "11.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/controls/-/controls-11.2.14.tgz", + "integrity": "sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/controls/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/core": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/@reactflow/core/-/core-11.11.4.tgz", + "integrity": "sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==", + "license": "MIT", + "dependencies": { + "@types/d3": "^7.4.0", + "@types/d3-drag": "^3.0.1", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/core/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/minimap": { + "version": "11.7.14", + "resolved": "https://registry.npmjs.org/@reactflow/minimap/-/minimap-11.7.14.tgz", + "integrity": "sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "@types/d3-selection": "^3.0.3", + "@types/d3-zoom": "^3.0.1", + "classcat": "^5.0.3", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/minimap/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/node-resizer": { + "version": "2.2.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-resizer/-/node-resizer-2.2.14.tgz", + "integrity": "sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.4", + "d3-drag": "^3.0.0", + "d3-selection": "^3.0.0", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-resizer/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reactflow/node-toolbar": { + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/@reactflow/node-toolbar/-/node-toolbar-1.3.14.tgz", + "integrity": "sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==", + "license": "MIT", + "dependencies": { + "@reactflow/core": "11.11.4", + "classcat": "^5.0.3", + "zustand": "^4.4.1" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@reactflow/node-toolbar/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.3", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.3.tgz", + "integrity": "sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "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.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT" + }, + "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/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/d3": { + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz", + "integrity": "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/d3-axis": "*", + "@types/d3-brush": "*", + "@types/d3-chord": "*", + "@types/d3-color": "*", + "@types/d3-contour": "*", + "@types/d3-delaunay": "*", + "@types/d3-dispatch": "*", + "@types/d3-drag": "*", + "@types/d3-dsv": "*", + "@types/d3-ease": "*", + "@types/d3-fetch": "*", + "@types/d3-force": "*", + "@types/d3-format": "*", + "@types/d3-geo": "*", + "@types/d3-hierarchy": "*", + "@types/d3-interpolate": "*", + "@types/d3-path": "*", + "@types/d3-polygon": "*", + "@types/d3-quadtree": "*", + "@types/d3-random": "*", + "@types/d3-scale": "*", + "@types/d3-scale-chromatic": "*", + "@types/d3-selection": "*", + "@types/d3-shape": "*", + "@types/d3-time": "*", + "@types/d3-time-format": "*", + "@types/d3-timer": "*", + "@types/d3-transition": "*", + "@types/d3-zoom": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-axis": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-axis/-/d3-axis-3.0.6.tgz", + "integrity": "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-brush": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-brush/-/d3-brush-3.0.6.tgz", + "integrity": "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-chord": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-chord/-/d3-chord-3.0.6.tgz", + "integrity": "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==", + "license": "MIT" + }, + "node_modules/@types/d3-dispatch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dispatch/-/d3-dispatch-3.0.7.tgz", + "integrity": "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==", + "license": "MIT" + }, + "node_modules/@types/d3-drag": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-drag/-/d3-drag-3.0.7.tgz", + "integrity": "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-dsv": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-dsv/-/d3-dsv-3.0.7.tgz", + "integrity": "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-fetch": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@types/d3-fetch/-/d3-fetch-3.0.7.tgz", + "integrity": "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==", + "license": "MIT", + "dependencies": { + "@types/d3-dsv": "*" + } + }, + "node_modules/@types/d3-force": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@types/d3-force/-/d3-force-3.0.10.tgz", + "integrity": "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-geo": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.0.tgz", + "integrity": "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==", + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-hierarchy": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/@types/d3-hierarchy/-/d3-hierarchy-3.1.7.tgz", + "integrity": "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-polygon": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-polygon/-/d3-polygon-3.0.2.tgz", + "integrity": "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==", + "license": "MIT" + }, + "node_modules/@types/d3-quadtree": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-quadtree/-/d3-quadtree-3.0.6.tgz", + "integrity": "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==", + "license": "MIT" + }, + "node_modules/@types/d3-random": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-random/-/d3-random-3.0.3.tgz", + "integrity": "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==", + "license": "MIT" + }, + "node_modules/@types/d3-selection": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/@types/d3-selection/-/d3-selection-3.0.11.tgz", + "integrity": "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==", + "license": "MIT" + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/d3-transition": { + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-transition/-/d3-transition-3.0.9.tgz", + "integrity": "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==", + "license": "MIT", + "dependencies": { + "@types/d3-selection": "*" + } + }, + "node_modules/@types/d3-zoom": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-zoom/-/d3-zoom-3.0.8.tgz", + "integrity": "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==", + "license": "MIT", + "dependencies": { + "@types/d3-interpolate": "*", + "@types/d3-selection": "*" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "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==", + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "22.19.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.10.tgz", + "integrity": "sha512-tF5VOugLS/EuDlTBijk0MqABfP8UxgYazTLo3uIn3b4yJgg26QRbVYJYsDtHrjdDUIRfP70+VfhTTc+CE1yskw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.13.tgz", + "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.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.54.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==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3" + }, + "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.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" + }, + "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.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.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.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "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.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.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.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "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.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.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==", + "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==", + "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.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.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.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.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/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "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/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@xyflow/react": { + "version": "12.10.0", + "resolved": "https://registry.npmjs.org/@xyflow/react/-/react-12.10.0.tgz", + "integrity": "sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==", + "license": "MIT", + "dependencies": { + "@xyflow/system": "0.0.74", + "classcat": "^5.0.3", + "zustand": "^4.4.0" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/@xyflow/react/node_modules/zustand": { + "version": "4.5.7", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.7.tgz", + "integrity": "sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.2.2" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@types/react": ">=16.8", + "immer": ">=9.0.6", + "react": ">=16.8" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/@xyflow/system": { + "version": "0.0.74", + "resolved": "https://registry.npmjs.org/@xyflow/system/-/system-0.0.74.tgz", + "integrity": "sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==", + "license": "MIT", + "dependencies": { + "@types/d3-drag": "^3.0.7", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-selection": "^3.0.10", + "@types/d3-transition": "^3.0.8", + "@types/d3-zoom": "^3.0.8", + "d3-drag": "^3.0.0", + "d3-interpolate": "^3.0.1", + "d3-selection": "^3.0.0", + "d3-zoom": "^3.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==", + "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==", + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "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-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "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==", + "license": "Python-2.0" + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/autoprefixer": { + "version": "10.4.24", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.24.tgz", + "integrity": "sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001766", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.11.1", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz", + "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==", + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/better-sqlite3": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz", + "integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "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/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001769", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001769.tgz", + "integrity": "sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==", + "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/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/classcat": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", + "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", + "license": "MIT" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/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==", + "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==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cssstyle": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz", + "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^3.2.0", + "rrweb-cssom": "^0.8.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dispatch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", + "integrity": "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-drag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-3.0.0.tgz", + "integrity": "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-selection": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", + "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-transition": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-transition/-/d3-transition-3.0.1.tgz", + "integrity": "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-dispatch": "1 - 3", + "d3-ease": "1 - 3", + "d3-interpolate": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "d3-selection": "2 - 3" + } + }, + "node_modules/d3-zoom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-zoom/-/d3-zoom-3.0.0.tgz", + "integrity": "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "2 - 3", + "d3-transition": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "license": "BSD-2-Clause" + }, + "node_modules/data-urls": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz", + "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "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==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.286", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.286.tgz", + "integrity": "sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-toolkit": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.44.0.tgz", + "integrity": "sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, + "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==", + "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==", + "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==", + "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-config-next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.1.6.tgz", + "integrity": "sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==", + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.1.6", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz", + "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==", + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "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/node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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==", + "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==", + "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.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "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==", + "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==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.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==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.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==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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==", + "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==", + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "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==", + "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==", + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.6.tgz", + "integrity": "sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==", + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "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==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/globrex": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/globrex/-/globrex-0.1.2.tgz", + "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", + "dev": true, + "license": "MIT" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "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==", + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-bun-module/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "26.1.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", + "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssstyle": "^4.2.1", + "data-urls": "^5.0.0", + "decimal.js": "^10.5.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.16", + "parse5": "^7.2.1", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^5.1.1", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^14.1.1", + "ws": "^8.18.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "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==", + "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==", + "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==", + "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==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" + }, + "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==", + "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==", + "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/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "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==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "bin": { + "lz-string": "bin/bin.js" + } + }, + "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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "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/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "license": "MIT" + }, + "node_modules/next": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", + "license": "MIT", + "dependencies": { + "@next/env": "16.1.6", + "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.8.3", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "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/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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==", + "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==", + "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==", + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/playwright": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz", + "integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.2" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.2", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz", + "integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/pretty-format/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT", + "peer": true + }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "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/reactflow": { + "version": "11.11.4", + "resolved": "https://registry.npmjs.org/reactflow/-/reactflow-11.11.4.tgz", + "integrity": "sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==", + "license": "MIT", + "dependencies": { + "@reactflow/background": "11.3.14", + "@reactflow/controls": "11.2.14", + "@reactflow/core": "11.11.4", + "@reactflow/minimap": "11.7.14", + "@reactflow/node-resizer": "2.2.14", + "@reactflow/node-toolbar": "1.3.14" + }, + "peerDependencies": { + "react": ">=17", + "react-dom": ">=17" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.7.0.tgz", + "integrity": "sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "1.x.x || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, + "node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "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.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "optional": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "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==", + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "license": "MIT" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-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==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/tailwindcss/node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "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/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.86.tgz", + "integrity": "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^6.1.86" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "6.1.86", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.86.tgz", + "integrity": "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tough-cookie": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.1.2.tgz", + "integrity": "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^6.1.32" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" + }, + "node_modules/tsconfck": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", + "integrity": "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==", + "dev": true, + "license": "MIT", + "bin": { + "tsconfck": "bin/tsconfck.js" + }, + "engines": { + "node": "^18 || >=20" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", + "license": "MIT", + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "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/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "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==", + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.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/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, + "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==", + "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==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "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/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-tsconfig-paths": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", + "integrity": "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "globrex": "^0.1.2", + "tsconfck": "^3.0.3" + }, + "peerDependencies": { + "vite": "*" + }, + "peerDependenciesMeta": { + "vite": { + "optional": true + } + } + }, + "node_modules/vite/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/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "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==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "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==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.11.tgz", + "integrity": "sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..e40ac01f9fc272651ed9b47437877895e17b2b88 --- /dev/null +++ b/package.json @@ -0,0 +1,66 @@ +{ + "name": "mission-control", + "version": "1.0.0", + "description": "OpenClaw Mission Control — open-source agent orchestration dashboard", + "scripts": { + "dev": "next dev --hostname 127.0.0.1", + "build": "next build", + "start": "next start --hostname 0.0.0.0 --port 3005", + "lint": "eslint .", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:e2e": "playwright test", + "test:all": "pnpm lint && pnpm typecheck && pnpm test && pnpm test:e2e && pnpm build", + "quality:gate": "pnpm test:all" + }, + "dependencies": { + "@xyflow/react": "^12.10.0", + "autoprefixer": "^10.4.20", + "better-sqlite3": "^12.6.2", + "clsx": "^2.1.1", + "eslint": "^9.18.0", + "eslint-config-next": "^16.1.6", + "next": "^16.1.6", + "next-themes": "^0.4.6", + "postcss": "^8.5.2", + "react": "^19.0.1", + "react-dom": "^19.0.1", + "reactflow": "^11.11.4", + "recharts": "^3.7.0", + "tailwind-merge": "^3.4.0", + "tailwindcss": "^3.4.17", + "typescript": "^5.7.2", + "ws": "^8.19.0", + "zustand": "^5.0.11" + }, + "devDependencies": { + "@playwright/test": "^1.51.0", + "@testing-library/dom": "^10.4.0", + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^16.1.0", + "@types/better-sqlite3": "^7.6.13", + "@types/node": "^22.10.6", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@types/ws": "^8.18.1", + "@vitejs/plugin-react": "^4.3.4", + "jsdom": "^26.0.0", + "vite-tsconfig-paths": "^5.1.4", + "vitest": "^2.1.5" + }, + "keywords": [ + "openclaw", + "agent", + "orchestration", + "dashboard", + "nextjs" + ], + "author": "Builderz Labs", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/builderz-labs/mission-control.git" + } +} diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..e43fa2140e9a7ebdfa9b910475babb051fb7f4be --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,18 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: 'tests', + timeout: 60_000, + expect: { + timeout: 10_000 + }, + fullyParallel: true, + reporter: [['list']], + use: { + baseURL: process.env.E2E_BASE_URL || 'http://127.0.0.1:3000', + trace: 'retain-on-failure' + }, + projects: [ + { name: 'chromium', use: { ...devices['Desktop Chrome'] } } + ] +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000000000000000000000000000000000000..16fdfbea351b6a640a6b42d88b8d91d7eea487c8 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,6568 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@xyflow/react': + specifier: ^12.10.0 + version: 12.10.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + autoprefixer: + specifier: ^10.4.20 + version: 10.4.24(postcss@8.5.6) + better-sqlite3: + specifier: ^12.6.2 + version: 12.6.2 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + eslint: + specifier: ^9.18.0 + version: 9.39.2(jiti@1.21.7) + eslint-config-next: + specifier: ^16.1.6 + version: 16.1.6(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + next: + specifier: ^16.1.6 + version: 16.1.6(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + next-themes: + specifier: ^0.4.6 + version: 0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + postcss: + specifier: ^8.5.2 + version: 8.5.6 + react: + specifier: ^19.0.1 + version: 19.2.4 + react-dom: + specifier: ^19.0.1 + version: 19.2.4(react@19.2.4) + reactflow: + specifier: ^11.11.4 + version: 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + recharts: + specifier: ^3.7.0 + version: 3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1) + tailwind-merge: + specifier: ^3.4.0 + version: 3.4.0 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.19 + typescript: + specifier: ^5.7.2 + version: 5.9.3 + ws: + specifier: ^8.19.0 + version: 8.19.0 + zustand: + specifier: ^5.0.11 + version: 5.0.11(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)) + devDependencies: + '@playwright/test': + specifier: ^1.51.0 + version: 1.58.2 + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.1 + '@testing-library/jest-dom': + specifier: ^6.6.3 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.1.0 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/better-sqlite3': + specifier: ^7.6.13 + version: 7.6.13 + '@types/node': + specifier: ^22.10.6 + version: 22.19.9 + '@types/react': + specifier: ^19.0.8 + version: 19.2.13 + '@types/react-dom': + specifier: ^19.0.3 + version: 19.2.3(@types/react@19.2.13) + '@types/ws': + specifier: ^8.18.1 + version: 8.18.1 + '@vitejs/plugin-react': + specifier: ^4.3.4 + version: 4.7.0(vite@5.4.21(@types/node@22.19.9)) + jsdom: + specifier: ^26.0.0 + version: 26.1.0 + vite-tsconfig-paths: + specifier: ^5.1.4 + version: 5.1.4(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.9)) + vitest: + specifier: ^2.1.5 + version: 2.1.9(@types/node@22.19.9)(jsdom@26.1.0) + +packages: + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@csstools/color-helpers@5.1.0': + resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.1.0': + resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@emnapi/core@1.8.1': + resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + + '@emnapi/wasi-threads@1.1.0': + resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.1': + resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.3': + resolution: {integrity: sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.2': + resolution: {integrity: sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.7': + resolution: {integrity: sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@0.2.12': + resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} + + '@next/env@16.1.6': + resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} + + '@next/eslint-plugin-next@16.1.6': + resolution: {integrity: sha512-/Qq3PTagA6+nYVfryAtQ7/9FEr/6YVyvOtl6rZnGsbReGLf0jZU6gkpr1FuChAQpvV46a78p4cmHOVP8mbfSMQ==} + + '@next/swc-darwin-arm64@16.1.6': + resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.1.6': + resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.1.6': + resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-arm64-musl@16.1.6': + resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@next/swc-linux-x64-gnu@16.1.6': + resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@next/swc-linux-x64-musl@16.1.6': + resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@next/swc-win32-arm64-msvc@16.1.6': + resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.1.6': + resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@nolyfill/is-core-module@1.0.39': + resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} + engines: {node: '>=12.4.0'} + + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} + engines: {node: '>=18'} + hasBin: true + + '@reactflow/background@11.3.14': + resolution: {integrity: sha512-Gewd7blEVT5Lh6jqrvOgd4G6Qk17eGKQfsDXgyRSqM+CTwDqRldG2LsWN4sNeno6sbqVIC2fZ+rAUBFA9ZEUDA==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/controls@11.2.14': + resolution: {integrity: sha512-MiJp5VldFD7FrqaBNIrQ85dxChrG6ivuZ+dcFhPQUwOK3HfYgX2RHdBua+gx+40p5Vw5It3dVNp/my4Z3jF0dw==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/core@11.11.4': + resolution: {integrity: sha512-H4vODklsjAq3AMq6Np4LE12i1I4Ta9PrDHuBR9GmL8uzTt2l2jh4CiQbEMpvMDcp7xi4be0hgXj+Ysodde/i7Q==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/minimap@11.7.14': + resolution: {integrity: sha512-mpwLKKrEAofgFJdkhwR5UQ1JYWlcAAL/ZU/bctBkuNTT1yqV+y0buoNVImsRehVYhJwffSWeSHaBR5/GJjlCSQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/node-resizer@2.2.14': + resolution: {integrity: sha512-fwqnks83jUlYr6OHcdFEedumWKChTHRGw/kbCxj0oqBd+ekfs+SIp4ddyNU0pdx96JIm5iNFS0oNrmEiJbbSaA==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reactflow/node-toolbar@1.3.14': + resolution: {integrity: sha512-rbynXQnH/xFNu4P9H+hVqlEUafDCkEoCy0Dg9mG22Sg+rY/0ck6KkrAQrYrTgXusd+cEJOMK0uOOFCK2/5rSGQ==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@reduxjs/toolkit@2.11.2': + resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 || ^19 + react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.57.1': + resolution: {integrity: sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.57.1': + resolution: {integrity: sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.57.1': + resolution: {integrity: sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.57.1': + resolution: {integrity: sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.57.1': + resolution: {integrity: sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.57.1': + resolution: {integrity: sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.57.1': + resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.57.1': + resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.57.1': + resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.57.1': + resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.57.1': + resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.57.1': + resolution: {integrity: sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + resolution: {integrity: sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + resolution: {integrity: sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.57.1': + resolution: {integrity: sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.57.1': + resolution: {integrity: sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==} + cpu: [x64] + os: [win32] + + '@rtsao/scc@1.1.0': + resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@standard-schema/utils@0.3.0': + resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@tybys/wasm-util@0.10.1': + resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/better-sqlite3@7.6.13': + resolution: {integrity: sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-axis@3.0.6': + resolution: {integrity: sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw==} + + '@types/d3-brush@3.0.6': + resolution: {integrity: sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A==} + + '@types/d3-chord@3.0.6': + resolution: {integrity: sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-contour@3.0.6': + resolution: {integrity: sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==} + + '@types/d3-delaunay@6.0.4': + resolution: {integrity: sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==} + + '@types/d3-dispatch@3.0.7': + resolution: {integrity: sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA==} + + '@types/d3-drag@3.0.7': + resolution: {integrity: sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ==} + + '@types/d3-dsv@3.0.7': + resolution: {integrity: sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-fetch@3.0.7': + resolution: {integrity: sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA==} + + '@types/d3-force@3.0.10': + resolution: {integrity: sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw==} + + '@types/d3-format@3.0.4': + resolution: {integrity: sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==} + + '@types/d3-geo@3.1.0': + resolution: {integrity: sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ==} + + '@types/d3-hierarchy@3.1.7': + resolution: {integrity: sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-polygon@3.0.2': + resolution: {integrity: sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA==} + + '@types/d3-quadtree@3.0.6': + resolution: {integrity: sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg==} + + '@types/d3-random@3.0.3': + resolution: {integrity: sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ==} + + '@types/d3-scale-chromatic@3.1.0': + resolution: {integrity: sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-selection@3.0.11': + resolution: {integrity: sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time-format@4.0.3': + resolution: {integrity: sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/d3-transition@3.0.9': + resolution: {integrity: sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg==} + + '@types/d3-zoom@3.0.8': + resolution: {integrity: sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw==} + + '@types/d3@7.4.3': + resolution: {integrity: sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/geojson@7946.0.16': + resolution: {integrity: sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/json5@0.0.29': + resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + + '@types/node@22.19.9': + resolution: {integrity: sha512-PD03/U8g1F9T9MI+1OBisaIARhSzeidsUjQaf51fOxrfjeiKN9bLVO06lHuHYjxdnqLWJijJHfqXPSJri2EM2A==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.13': + resolution: {integrity: sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==} + + '@types/use-sync-external-store@0.0.6': + resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@typescript-eslint/eslint-plugin@8.54.0': + resolution: {integrity: sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.54.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/parser@8.54.0': + resolution: {integrity: sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/project-service@8.54.0': + resolution: {integrity: sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/scope-manager@8.54.0': + resolution: {integrity: sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.54.0': + resolution: {integrity: sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/type-utils@8.54.0': + resolution: {integrity: sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/types@8.54.0': + resolution: {integrity: sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.54.0': + resolution: {integrity: sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/utils@8.54.0': + resolution: {integrity: sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + '@typescript-eslint/visitor-keys@8.54.0': + resolution: {integrity: sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.11.1': + resolution: {integrity: sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + resolution: {integrity: sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.11.1': + resolution: {integrity: sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + resolution: {integrity: sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + resolution: {integrity: sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + resolution: {integrity: sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + resolution: {integrity: sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + resolution: {integrity: sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + resolution: {integrity: sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==} + cpu: [x64] + os: [win32] + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@2.1.9': + resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==} + + '@vitest/mocker@2.1.9': + resolution: {integrity: sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@2.1.9': + resolution: {integrity: sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==} + + '@vitest/runner@2.1.9': + resolution: {integrity: sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==} + + '@vitest/snapshot@2.1.9': + resolution: {integrity: sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==} + + '@vitest/spy@2.1.9': + resolution: {integrity: sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==} + + '@vitest/utils@2.1.9': + resolution: {integrity: sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==} + + '@xyflow/react@12.10.0': + resolution: {integrity: sha512-eOtz3whDMWrB4KWVatIBrKuxECHqip6PfA8fTpaS2RUGVpiEAe+nqDKsLqkViVWxDGreq0lWX71Xth/SPAzXiw==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + '@xyflow/system@0.0.74': + resolution: {integrity: sha512-7v7B/PkiVrkdZzSbL+inGAo6tkR/WQHHG0/jhSvLQToCsfa8YubOGmBYd1s08tpKpihdHDZFwzQZeR69QSBb4Q==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.findlastindex@1.2.6: + resolution: {integrity: sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-types-flow@0.0.8: + resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + autoprefixer@10.4.24: + resolution: {integrity: sha512-uHZg7N9ULTVbutaIsDRoUkoS8/h3bdsmVJYZ5l3wv8Cp/6UIIoRDm90hZ+BwxUj/hGBEzLxdHNSKuFpn8WOyZw==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + axe-core@4.11.1: + resolution: {integrity: sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==} + engines: {node: '>=4'} + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.9.19: + resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==} + hasBin: true + + better-sqlite3@12.6.2: + resolution: {integrity: sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==} + engines: {node: 20.x || 22.x || 23.x || 24.x || 25.x} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bindings@1.5.0: + resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + caniuse-lite@1.0.30001769: + resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} + + chai@5.3.3: + resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@2.1.3: + resolution: {integrity: sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + + damerau-levenshtein@1.0.8: + resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + debug@3.2.7: + resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + electron-to-chromium@1.5.286: + resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.2: + resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + es-toolkit@1.44.0: + resolution: {integrity: sha512-6penXeZalaV88MM3cGkFZZfOoLGWshWWfdy0tWw/RlVVyhvMaWSBTOvXNeiW3e5FwdS5ePW0LGEu17zT139ktg==} + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-next@16.1.6: + resolution: {integrity: sha512-vKq40io2B0XtkkNDYyleATwblNt8xuh3FWp8SpSz3pt7P01OkBFlKsJZ2mWt5WsCySlDQLckb1zMY9yE9Qy0LA==} + peerDependencies: + eslint: '>=9.0.0' + typescript: '>=3.3.1' + peerDependenciesMeta: + typescript: + optional: true + + eslint-import-resolver-node@0.3.9: + resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + + eslint-import-resolver-typescript@3.10.1: + resolution: {integrity: sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '*' + eslint-plugin-import: '*' + eslint-plugin-import-x: '*' + peerDependenciesMeta: + eslint-plugin-import: + optional: true + eslint-plugin-import-x: + optional: true + + eslint-module-utils@2.12.1: + resolution: {integrity: sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: '*' + eslint-import-resolver-node: '*' + eslint-import-resolver-typescript: '*' + eslint-import-resolver-webpack: '*' + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + eslint: + optional: true + eslint-import-resolver-node: + optional: true + eslint-import-resolver-typescript: + optional: true + eslint-import-resolver-webpack: + optional: true + + eslint-plugin-import@2.32.0: + resolution: {integrity: sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==} + engines: {node: '>=4'} + peerDependencies: + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true + + eslint-plugin-jsx-a11y@6.10.2: + resolution: {integrity: sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==} + engines: {node: '>=4.0'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9 + + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint@9.39.2: + resolution: {integrity: sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.1: + resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + engines: {node: '>=8.6.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + file-uri-to-path@1.0.0: + resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + get-tsconfig@4.13.6: + resolution: {integrity: sha512-shZT/QMiSHc/YBLxxOkMtgSid5HFoauqCE3/exfsEcwg1WkeqjG+V40yBbBrsD+jW2HDXcs28xOfcbm2jI8Ddw==} + + github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@16.4.0: + resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} + engines: {node: '>=18'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + immer@11.1.3: + resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-bun-module@2.0.0: + resolution: {integrity: sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@1.0.2: + resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + language-subtag-registry@0.3.23: + resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} + + language-tags@1.0.9: + resolution: {integrity: sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==} + engines: {node: '>=0.10'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + loupe@3.2.1: + resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.2: + resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-build-utils@2.0.0: + resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==} + + napi-postinstall@0.3.4: + resolution: {integrity: sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + next-themes@0.4.6: + resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} + peerDependencies: + react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc + + next@16.1.6: + resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + node-abi@3.87.0: + resolution: {integrity: sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==} + engines: {node: '>=10'} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.groupby@1.0.3: + resolution: {integrity: sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + engines: {node: '>=18'} + hasBin: true + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prebuild-install@7.1.3: + resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} + engines: {node: '>=10'} + hasBin: true + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-redux@9.2.0: + resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} + peerDependencies: + '@types/react': ^18.2.25 || ^19 + react: ^18.0 || ^19 + redux: ^5.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + redux: + optional: true + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + reactflow@11.11.4: + resolution: {integrity: sha512-70FOtJkUWH3BAOsN+LU9lCrKoKbtOPnz2uq0CV2PLdNSwxTXOhCbsZr50GmZ+Rtw3jx8Uv7/vBFtCGixLfd4Og==} + peerDependencies: + react: '>=17' + react-dom: '>=17' + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + recharts@3.7.0: + resolution: {integrity: sha512-l2VCsy3XXeraxIID9fx23eCb6iCBsxUQDnE8tWm6DFdszVAO7WVY/ChAD9wVit01y6B2PMupYiMmQwhgPHc9Ew==} + engines: {node: '>=18'} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + redux-thunk@3.1.0: + resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} + peerDependencies: + redux: ^5.0.0 + + redux@5.0.1: + resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + reselect@5.1.1: + resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.57.1: + resolution: {integrity: sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + + simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stable-hash@0.0.5: + resolution: {integrity: sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string.prototype.includes@2.0.1: + resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} + engines: {node: '>= 0.4'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwind-merge@3.4.0: + resolution: {integrity: sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} + engines: {node: '>=14.0.0'} + + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + ts-api-utils@2.4.0: + resolution: {integrity: sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tsconfig-paths@3.15.0: + resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript-eslint@8.54.0: + resolution: {integrity: sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <6.0.0' + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + unrs-resolver@1.11.1: + resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + victory-vendor@37.3.6: + resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} + + vite-node@2.1.9: + resolution: {integrity: sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite-tsconfig-paths@5.1.4: + resolution: {integrity: sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w==} + peerDependencies: + vite: '*' + peerDependenciesMeta: + vite: + optional: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + 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 + + vitest@2.1.9: + resolution: {integrity: sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/node': ^18.0.0 || >=20.0.0 + '@vitest/browser': 2.1.9 + '@vitest/ui': 2.1.9 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + + zustand@5.0.11: + resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@adobe/css-tools@4.4.4': {} + + '@alloc/quick-lru@5.2.0': {} + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@csstools/color-helpers@5.1.0': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.1.0 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@emnapi/core@1.8.1': + dependencies: + '@emnapi/wasi-threads': 1.1.0 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.1.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.2(jiti@1.21.7))': + dependencies: + eslint: 9.39.2(jiti@1.21.7) + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.1': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.2 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.3': + dependencies: + ajv: 6.12.6 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.2 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.2': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.7': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.4.3 + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@0.2.12': + dependencies: + '@emnapi/core': 1.8.1 + '@emnapi/runtime': 1.8.1 + '@tybys/wasm-util': 0.10.1 + optional: true + + '@next/env@16.1.6': {} + + '@next/eslint-plugin-next@16.1.6': + dependencies: + fast-glob: 3.3.1 + + '@next/swc-darwin-arm64@16.1.6': + optional: true + + '@next/swc-darwin-x64@16.1.6': + optional: true + + '@next/swc-linux-arm64-gnu@16.1.6': + optional: true + + '@next/swc-linux-arm64-musl@16.1.6': + optional: true + + '@next/swc-linux-x64-gnu@16.1.6': + optional: true + + '@next/swc-linux-x64-musl@16.1.6': + optional: true + + '@next/swc-win32-arm64-msvc@16.1.6': + optional: true + + '@next/swc-win32-x64-msvc@16.1.6': + optional: true + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@nolyfill/is-core-module@1.0.39': {} + + '@playwright/test@1.58.2': + dependencies: + playwright: 1.58.2 + + '@reactflow/background@11.3.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + classcat: 5.0.5 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/controls@11.2.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + classcat: 5.0.5 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/core@11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@types/d3': 7.4.3 + '@types/d3-drag': 3.0.7 + '@types/d3-selection': 3.0.11 + '@types/d3-zoom': 3.0.8 + classcat: 5.0.5 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/minimap@11.7.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/d3-selection': 3.0.11 + '@types/d3-zoom': 3.0.8 + classcat: 5.0.5 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/node-resizer@2.2.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + classcat: 5.0.5 + d3-drag: 3.0.0 + d3-selection: 3.0.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reactflow/node-toolbar@1.3.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@reactflow/core': 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + classcat: 5.0.5 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1))(react@19.2.4)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@standard-schema/utils': 0.3.0 + immer: 11.1.3 + redux: 5.0.1 + redux-thunk: 3.1.0(redux@5.0.1) + reselect: 5.1.1 + optionalDependencies: + react: 19.2.4 + react-redux: 9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1) + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.57.1': + optional: true + + '@rollup/rollup-android-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-arm64@4.57.1': + optional: true + + '@rollup/rollup-darwin-x64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-arm64@4.57.1': + optional: true + + '@rollup/rollup-freebsd-x64@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.57.1': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-linux-x64-musl@4.57.1': + optional: true + + '@rollup/rollup-openbsd-x64@4.57.1': + optional: true + + '@rollup/rollup-openharmony-arm64@4.57.1': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.57.1': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.57.1': + optional: true + + '@rtsao/scc@1.1.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@standard-schema/utils@0.3.0': {} + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.13))(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@babel/runtime': 7.28.6 + '@testing-library/dom': 10.4.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.13 + '@types/react-dom': 19.2.3(@types/react@19.2.13) + + '@tybys/wasm-util@0.10.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/better-sqlite3@7.6.13': + dependencies: + '@types/node': 22.19.9 + + '@types/d3-array@3.2.2': {} + + '@types/d3-axis@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-brush@3.0.6': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-chord@3.0.6': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-contour@3.0.6': + dependencies: + '@types/d3-array': 3.2.2 + '@types/geojson': 7946.0.16 + + '@types/d3-delaunay@6.0.4': {} + + '@types/d3-dispatch@3.0.7': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-dsv@3.0.7': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-fetch@3.0.7': + dependencies: + '@types/d3-dsv': 3.0.7 + + '@types/d3-force@3.0.10': {} + + '@types/d3-format@3.0.4': {} + + '@types/d3-geo@3.1.0': + dependencies: + '@types/geojson': 7946.0.16 + + '@types/d3-hierarchy@3.1.7': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-polygon@3.0.2': {} + + '@types/d3-quadtree@3.0.6': {} + + '@types/d3-random@3.0.3': {} + + '@types/d3-scale-chromatic@3.1.0': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time-format@4.0.3': {} + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + + '@types/d3@7.4.3': + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-axis': 3.0.6 + '@types/d3-brush': 3.0.6 + '@types/d3-chord': 3.0.6 + '@types/d3-color': 3.1.3 + '@types/d3-contour': 3.0.6 + '@types/d3-delaunay': 6.0.4 + '@types/d3-dispatch': 3.0.7 + '@types/d3-drag': 3.0.7 + '@types/d3-dsv': 3.0.7 + '@types/d3-ease': 3.0.2 + '@types/d3-fetch': 3.0.7 + '@types/d3-force': 3.0.10 + '@types/d3-format': 3.0.4 + '@types/d3-geo': 3.1.0 + '@types/d3-hierarchy': 3.1.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-path': 3.1.1 + '@types/d3-polygon': 3.0.2 + '@types/d3-quadtree': 3.0.6 + '@types/d3-random': 3.0.3 + '@types/d3-scale': 4.0.9 + '@types/d3-scale-chromatic': 3.1.0 + '@types/d3-selection': 3.0.11 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-time-format': 4.0.3 + '@types/d3-timer': 3.0.2 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + + '@types/estree@1.0.8': {} + + '@types/geojson@7946.0.16': {} + + '@types/json-schema@7.0.15': {} + + '@types/json5@0.0.29': {} + + '@types/node@22.19.9': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.13)': + dependencies: + '@types/react': 19.2.13 + + '@types/react@19.2.13': + dependencies: + csstype: 3.2.3 + + '@types/use-sync-external-store@0.0.6': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 22.19.9 + + '@typescript-eslint/eslint-plugin@8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/type-utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 + eslint: 9.39.2(jiti@1.21.7) + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + eslint: 9.39.2(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.54.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + debug: 4.4.3 + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + + '@typescript-eslint/tsconfig-utils@8.54.0(typescript@5.9.3)': + dependencies: + typescript: 5.9.3 + + '@typescript-eslint/type-utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + debug: 4.4.3 + eslint: 9.39.2(jiti@1.21.7) + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.54.0': {} + + '@typescript-eslint/typescript-estree@8.54.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/project-service': 8.54.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.54.0(typescript@5.9.3) + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/visitor-keys': 8.54.0 + debug: 4.4.3 + minimatch: 9.0.5 + semver: 7.7.4 + tinyglobby: 0.2.15 + ts-api-utils: 2.4.0(typescript@5.9.3) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) + '@typescript-eslint/scope-manager': 8.54.0 + '@typescript-eslint/types': 8.54.0 + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.54.0': + dependencies: + '@typescript-eslint/types': 8.54.0 + eslint-visitor-keys: 4.2.1 + + '@unrs/resolver-binding-android-arm-eabi@1.11.1': + optional: true + + '@unrs/resolver-binding-android-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.11.1': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.11.1': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.11.1': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.11.1': + dependencies: + '@napi-rs/wasm-runtime': 0.2.12 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.11.1': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.11.1': + optional: true + + '@vitejs/plugin-react@4.7.0(vite@5.4.21(@types/node@22.19.9))': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 5.4.21(@types/node@22.19.9) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@2.1.9': + dependencies: + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + tinyrainbow: 1.2.0 + + '@vitest/mocker@2.1.9(vite@5.4.21(@types/node@22.19.9))': + dependencies: + '@vitest/spy': 2.1.9 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 5.4.21(@types/node@22.19.9) + + '@vitest/pretty-format@2.1.9': + dependencies: + tinyrainbow: 1.2.0 + + '@vitest/runner@2.1.9': + dependencies: + '@vitest/utils': 2.1.9 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + magic-string: 0.30.21 + pathe: 1.1.2 + + '@vitest/spy@2.1.9': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.9': + dependencies: + '@vitest/pretty-format': 2.1.9 + loupe: 3.2.1 + tinyrainbow: 1.2.0 + + '@xyflow/react@12.10.0(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@xyflow/system': 0.0.74 + classcat: 5.0.5 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + zustand: 4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.74': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.findlastindex@1.2.6: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + assertion-error@2.0.1: {} + + ast-types-flow@0.0.8: {} + + async-function@1.0.0: {} + + autoprefixer@10.4.24(postcss@8.5.6): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001769 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + axe-core@4.11.1: {} + + axobject-query@4.1.0: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.9.19: {} + + better-sqlite3@12.6.2: + dependencies: + bindings: 1.5.0 + prebuild-install: 7.1.3 + + binary-extensions@2.3.0: {} + + bindings@1.5.0: + dependencies: + file-uri-to-path: 1.0.0 + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001769 + electron-to-chromium: 1.5.286 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + cac@6.7.14: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camelcase-css@2.0.1: {} + + caniuse-lite@1.0.30001769: {} + + chai@5.3.3: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.3 + deep-eql: 5.0.2 + loupe: 3.2.1 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@2.1.3: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@1.1.4: {} + + classcat@5.0.5: {} + + client-only@0.0.1: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@4.1.1: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css.escape@1.5.1: {} + + cssesc@3.0.0: {} + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-selection@3.0.0: {} + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + + damerau-levenshtein@1.0.8: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + debug@3.2.7: + dependencies: + ms: 2.1.3 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + decimal.js@10.6.0: {} + + decompress-response@6.0.0: + dependencies: + mimic-response: 3.1.0 + + deep-eql@5.0.2: {} + + deep-extend@0.6.0: {} + + deep-is@0.1.4: {} + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + didyoumean@1.2.2: {} + + dlv@1.1.3: {} + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + electron-to-chromium@1.5.286: {} + + emoji-regex@9.2.2: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + entities@6.0.1: {} + + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.2: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + es-toolkit@1.44.0: {} + + esbuild@0.21.5: + 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 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-next@16.1.6(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 16.1.6 + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7)) + eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@1.21.7)) + globals: 16.4.0 + typescript-eslint: 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-import-resolver-node@0.3.9: + dependencies: + debug: 3.2.7 + is-core-module: 2.16.1 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.2(jiti@1.21.7) + get-tsconfig: 4.13.6 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7)) + transitivePeerDependencies: + - supports-color + + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.2(jiti@1.21.7) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@1.21.7)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.11.1 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.39.2(jiti@1.21.7) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.2(jiti@1.21.7)): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + eslint: 9.39.2(jiti@1.21.7) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react@7.37.5(eslint@9.39.2(jiti@1.21.7)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.2 + eslint: 9.39.2(jiti@1.21.7) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint@9.39.2(jiti@1.21.7): + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.2(jiti@1.21.7)) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.1 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.3 + '@eslint/js': 9.39.2 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.7 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + 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.4 + optionalDependencies: + jiti: 1.21.7 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + expand-template@2.0.3: {} + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.1: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + file-uri-to-path@1.0.0: {} + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + fraction.js@5.3.4: {} + + fs-constants@1.0.0: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + get-tsconfig@4.13.6: + dependencies: + resolve-pkg-maps: 1.0.0 + + github-from-package@0.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@16.4.0: {} + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globrex@0.1.2: {} + + gopd@1.2.0: {} + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + immer@10.2.0: {} + + immer@11.1.3: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inherits@2.0.4: {} + + ini@1.3.8: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + internmap@2.0.3: {} + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-bun-module@2.0.0: + dependencies: + semver: 7.7.4 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.19.0 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@1.0.2: + dependencies: + minimist: 1.2.8 + + json5@2.2.3: {} + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + language-subtag-registry@0.3.23: {} + + language-tags@1.0.9: + dependencies: + language-subtag-registry: 0.3.23 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + loupe@3.2.1: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mimic-response@3.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.2: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + mkdirp-classic@0.5.3: {} + + ms@2.1.3: {} + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + napi-build-utils@2.0.0: {} + + napi-postinstall@0.3.4: {} + + natural-compare@1.4.0: {} + + next-themes@0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + next@16.1.6(@babel/core@7.29.0)(@playwright/test@1.58.2)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@next/env': 16.1.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.9.19 + caniuse-lite: 1.0.30001769 + postcss: 8.4.31 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.6 + '@next/swc-darwin-x64': 16.1.6 + '@next/swc-linux-arm64-gnu': 16.1.6 + '@next/swc-linux-arm64-musl': 16.1.6 + '@next/swc-linux-x64-gnu': 16.1.6 + '@next/swc-linux-x64-musl': 16.1.6 + '@next/swc-win32-arm64-msvc': 16.1.6 + '@next/swc-win32-x64-msvc': 16.1.6 + '@playwright/test': 1.58.2 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + node-abi@3.87.0: + dependencies: + semver: 7.7.4 + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + nwsapi@2.2.23: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + + object.groupby@1.0.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + pathe@1.1.2: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 + optionalDependencies: + fsevents: 2.3.2 + + possible-typed-array-names@1.1.0: {} + + postcss-import@15.1.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.6): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.6 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.6): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.6 + + postcss-nested@6.2.0(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prebuild-install@7.1.3: + dependencies: + detect-libc: 2.1.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 2.0.0 + node-abi: 3.87.0 + pump: 3.0.3 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.4 + tunnel-agent: 0.6.0 + + prelude-ls@1.2.1: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + queue-microtask@1.2.3: {} + + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-redux@9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1): + dependencies: + '@types/use-sync-external-store': 0.0.6 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.13 + redux: 5.0.1 + + react-refresh@0.17.0: {} + + react@19.2.4: {} + + reactflow@11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@reactflow/background': 11.3.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@reactflow/controls': 11.2.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@reactflow/core': 11.11.4(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@reactflow/minimap': 11.7.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@reactflow/node-resizer': 2.2.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@reactflow/node-toolbar': 1.3.14(@types/react@19.2.13)(immer@11.1.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - immer + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + recharts@3.7.0(@types/react@19.2.13)(react-dom@19.2.4(react@19.2.4))(react-is@17.0.2)(react@19.2.4)(redux@5.0.1): + dependencies: + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1))(react@19.2.4) + clsx: 2.1.1 + decimal.js-light: 2.5.1 + es-toolkit: 1.44.0 + eventemitter3: 5.0.4 + immer: 10.2.0 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-is: 17.0.2 + react-redux: 9.2.0(@types/react@19.2.13)(react@19.2.4)(redux@5.0.1) + reselect: 5.1.1 + tiny-invariant: 1.3.3 + use-sync-external-store: 1.6.0(react@19.2.4) + victory-vendor: 37.3.6 + transitivePeerDependencies: + - '@types/react' + - redux + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + redux-thunk@3.1.0(redux@5.0.1): + dependencies: + redux: 5.0.1 + + redux@5.0.1: {} + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + reselect@5.1.1: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + reusify@1.1.0: {} + + rollup@4.57.1: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.57.1 + '@rollup/rollup-android-arm64': 4.57.1 + '@rollup/rollup-darwin-arm64': 4.57.1 + '@rollup/rollup-darwin-x64': 4.57.1 + '@rollup/rollup-freebsd-arm64': 4.57.1 + '@rollup/rollup-freebsd-x64': 4.57.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.57.1 + '@rollup/rollup-linux-arm-musleabihf': 4.57.1 + '@rollup/rollup-linux-arm64-gnu': 4.57.1 + '@rollup/rollup-linux-arm64-musl': 4.57.1 + '@rollup/rollup-linux-loong64-gnu': 4.57.1 + '@rollup/rollup-linux-loong64-musl': 4.57.1 + '@rollup/rollup-linux-ppc64-gnu': 4.57.1 + '@rollup/rollup-linux-ppc64-musl': 4.57.1 + '@rollup/rollup-linux-riscv64-gnu': 4.57.1 + '@rollup/rollup-linux-riscv64-musl': 4.57.1 + '@rollup/rollup-linux-s390x-gnu': 4.57.1 + '@rollup/rollup-linux-x64-gnu': 4.57.1 + '@rollup/rollup-linux-x64-musl': 4.57.1 + '@rollup/rollup-openbsd-x64': 4.57.1 + '@rollup/rollup-openharmony-arm64': 4.57.1 + '@rollup/rollup-win32-arm64-msvc': 4.57.1 + '@rollup/rollup-win32-ia32-msvc': 4.57.1 + '@rollup/rollup-win32-x64-gnu': 4.57.1 + '@rollup/rollup-win32-x64-msvc': 4.57.1 + fsevents: 2.3.3 + + rrweb-cssom@0.8.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.4 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + siginfo@2.0.0: {} + + simple-concat@1.0.1: {} + + simple-get@4.0.1: + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + + source-map-js@1.2.1: {} + + stable-hash@0.0.5: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string.prototype.includes@2.0.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-bom@3.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@2.0.1: {} + + strip-json-comments@3.1.1: {} + + styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.4): + dependencies: + client-only: 0.0.1 + react: 19.2.4 + optionalDependencies: + '@babel/core': 7.29.0 + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + tailwind-merge@3.4.0: {} + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-import: 15.1.0(postcss@8.5.6) + postcss-js: 4.1.0(postcss@8.5.6) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.6) + postcss-nested: 6.2.0(postcss@8.5.6) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tiny-invariant@1.3.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.4.0(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + ts-interface-checker@0.1.13: {} + + tsconfck@3.1.6(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + tsconfig-paths@3.15.0: + dependencies: + '@types/json5': 0.0.29 + json5: 1.0.2 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + tunnel-agent@0.6.0: + dependencies: + safe-buffer: 5.2.1 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript-eslint@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.54.0(@typescript-eslint/parser@8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/typescript-estree': 8.54.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.54.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3) + eslint: 9.39.2(jiti@1.21.7) + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + typescript@5.9.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@6.21.0: {} + + unrs-resolver@1.11.1: + dependencies: + napi-postinstall: 0.3.4 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.11.1 + '@unrs/resolver-binding-android-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-arm64': 1.11.1 + '@unrs/resolver-binding-darwin-x64': 1.11.1 + '@unrs/resolver-binding-freebsd-x64': 1.11.1 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.11.1 + '@unrs/resolver-binding-linux-arm64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-arm64-musl': 1.11.1 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-riscv64-musl': 1.11.1 + '@unrs/resolver-binding-linux-s390x-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-gnu': 1.11.1 + '@unrs/resolver-binding-linux-x64-musl': 1.11.1 + '@unrs/resolver-binding-wasm32-wasi': 1.11.1 + '@unrs/resolver-binding-win32-arm64-msvc': 1.11.1 + '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 + '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-sync-external-store@1.6.0(react@19.2.4): + dependencies: + react: 19.2.4 + + util-deprecate@1.0.2: {} + + victory-vendor@37.3.6: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + vite-node@2.1.9(@types/node@22.19.9): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 1.1.2 + vite: 5.4.21(@types/node@22.19.9) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@5.4.21(@types/node@22.19.9)): + dependencies: + debug: 4.4.3 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@5.9.3) + optionalDependencies: + vite: 5.4.21(@types/node@22.19.9) + transitivePeerDependencies: + - supports-color + - typescript + + vite@5.4.21(@types/node@22.19.9): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.6 + rollup: 4.57.1 + optionalDependencies: + '@types/node': 22.19.9 + fsevents: 2.3.3 + + vitest@2.1.9(@types/node@22.19.9)(jsdom@26.1.0): + dependencies: + '@vitest/expect': 2.1.9 + '@vitest/mocker': 2.1.9(vite@5.4.21(@types/node@22.19.9)) + '@vitest/pretty-format': 2.1.9 + '@vitest/runner': 2.1.9 + '@vitest/snapshot': 2.1.9 + '@vitest/spy': 2.1.9 + '@vitest/utils': 2.1.9 + chai: 5.3.3 + debug: 4.4.3 + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 1.1.2 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.1.1 + tinyrainbow: 1.2.0 + vite: 5.4.21(@types/node@22.19.9) + vite-node: 2.1.9(@types/node@22.19.9) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.19.9 + jsdom: 26.1.0 + transitivePeerDependencies: + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@7.0.0: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrappy@1.0.2: {} + + ws@8.19.0: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} + + zustand@4.5.7(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.13 + immer: 11.1.3 + react: 19.2.4 + + zustand@5.0.11(@types/react@19.2.13)(immer@11.1.3)(react@19.2.4)(use-sync-external-store@1.6.0(react@19.2.4)): + optionalDependencies: + '@types/react': 19.2.13 + immer: 11.1.3 + react: 19.2.4 + use-sync-external-store: 1.6.0(react@19.2.4) diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000000000000000000000000000000000000..33ad091d26d8a9dc95ebdf616e217d985ec215b8 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/scripts/agent-heartbeat.sh b/scripts/agent-heartbeat.sh new file mode 100644 index 0000000000000000000000000000000000000000..4e9e5876b6dc5ddcf6fd262bb3ff59982ca9c2b6 --- /dev/null +++ b/scripts/agent-heartbeat.sh @@ -0,0 +1,240 @@ +#!/bin/bash + +# Mission Control Phase 3: Agent Heartbeat Script +# Called by OpenClaw cron every 15 minutes to wake agents and check for work +# +# Usage: +# scripts/agent-heartbeat.sh [agent_name] +# +# If no agent specified, checks all agents with session keys + +set -e + +# Configuration +MISSION_CONTROL_URL="${MISSION_CONTROL_URL:-http://localhost:3005}" +LOG_DIR="${LOG_DIR:-$HOME/.mission-control/logs}" +LOG_FILE="$LOG_DIR/agent-heartbeat-$(date +%Y-%m-%d).log" +MAX_CONCURRENT=3 # Max agents to check concurrently +OPENCLAW_CMD="${OPENCLAW_CMD:-openclaw}" + +# Ensure log directory exists +mkdir -p "$LOG_DIR" + +# Logging function +log() { + local level="$1" + shift + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE" +} + +# Check if Mission Control is running +check_mission_control() { + if ! curl -s "$MISSION_CONTROL_URL/api/status" > /dev/null 2>&1; then + log "ERROR" "Mission Control not accessible at $MISSION_CONTROL_URL" + return 1 + fi + return 0 +} + +# Check heartbeat for specific agent +check_agent_heartbeat() { + local agent_name="$1" + local agent_id="$2" + + log "INFO" "Checking heartbeat for agent: $agent_name" + + # Call heartbeat endpoint + local response + response=$(curl -s -w "HTTP_STATUS:%{http_code}" "$MISSION_CONTROL_URL/api/agents/$agent_id/heartbeat" 2>/dev/null) + + local http_code + http_code=$(echo "$response" | grep -o "HTTP_STATUS:[0-9]*" | cut -d: -f2) + local body + body=$(echo "$response" | sed 's/HTTP_STATUS:[0-9]*$//') + + if [[ "$http_code" != "200" ]]; then + log "ERROR" "Heartbeat failed for $agent_name: HTTP $http_code" + return 1 + fi + + # Parse response + local status + status=$(echo "$body" | jq -r '.status // "unknown"' 2>/dev/null || echo "parse_error") + + if [[ "$status" == "HEARTBEAT_OK" ]]; then + log "INFO" "Agent $agent_name: No work items found" + return 0 + elif [[ "$status" == "WORK_ITEMS_FOUND" ]]; then + local total_items + total_items=$(echo "$body" | jq -r '.total_items // 0' 2>/dev/null || echo "0") + log "INFO" "Agent $agent_name: Found $total_items work items" + + # If work items found and agent has session key, send wake notification + local session_key + session_key=$(get_agent_session_key "$agent_name") + + if [[ -n "$session_key" && "$session_key" != "null" ]]; then + send_wake_notification "$agent_name" "$session_key" "$total_items" "$body" + else + log "WARN" "Agent $agent_name has work items but no session key configured" + fi + + return 0 + else + log "ERROR" "Unexpected heartbeat response for $agent_name: $status" + return 1 + fi +} + +# Get agent session key from database +get_agent_session_key() { + local agent_name="$1" + + # Query agents API to get session key + local agent_data + agent_data=$(curl -s "$MISSION_CONTROL_URL/api/agents?limit=100" 2>/dev/null | jq -r ".agents[] | select(.name == \"$agent_name\") | .session_key" 2>/dev/null || echo "") + + echo "$agent_data" +} + +# Send wake notification to agent session +send_wake_notification() { + local agent_name="$1" + local session_key="$2" + local work_items_count="$3" + local heartbeat_data="$4" + + log "INFO" "Sending wake notification to $agent_name (session: $session_key)" + + # Format wake message + local wake_message="🤖 **Mission Control Heartbeat**\n\n" + wake_message+="Agent: $agent_name\n" + wake_message+="Work items found: $work_items_count\n\n" + wake_message+="🔔 You have notifications or tasks that need attention.\n" + wake_message+="Use Mission Control to view details: $MISSION_CONTROL_URL\n\n" + wake_message+="⏰ $(date '+%Y-%m-%d %H:%M:%S')" + + # Send via OpenClaw sessions_send + if "$OPENCLAW_CMD" gateway sessions_send --session "$session_key" --message "$wake_message" >> "$LOG_FILE" 2>&1; then + log "INFO" "Wake notification sent successfully to $agent_name" + else + log "ERROR" "Failed to send wake notification to $agent_name" + fi +} + +# Get list of agents to check +get_agents_to_check() { + local filter_agent="$1" + + if [[ -n "$filter_agent" ]]; then + # Check specific agent + echo "$filter_agent" + return + fi + + # Get all agents with session keys + curl -s "$MISSION_CONTROL_URL/api/agents?limit=100" 2>/dev/null | \ + jq -r '.agents[] | select(.session_key != null and .session_key != "") | .name' 2>/dev/null || \ + echo "" +} + +# Main execution +main() { + local target_agent="$1" + + log "INFO" "Starting agent heartbeat check (PID: $$)" + + # Check if Mission Control is running + if ! check_mission_control; then + log "ERROR" "Aborting: Mission Control not accessible" + exit 1 + fi + + # Get agents to check + local agents + agents=$(get_agents_to_check "$target_agent") + + if [[ -z "$agents" ]]; then + log "WARN" "No agents found with session keys configured" + exit 0 + fi + + local total_agents + total_agents=$(echo "$agents" | wc -l) + log "INFO" "Checking heartbeat for $total_agents agent(s)" + + # Process agents (limit concurrency) + local processed=0 + local successful=0 + local failed=0 + local pids=() + + while IFS= read -r agent_name; do + [[ -z "$agent_name" ]] && continue + + # Wait if we've reached max concurrent processes + while [[ ${#pids[@]} -ge $MAX_CONCURRENT ]]; do + for i in "${!pids[@]}"; do + if ! kill -0 "${pids[$i]}" 2>/dev/null; then + unset "pids[$i]" + fi + done + pids=("${pids[@]}") # Reindex array + + if [[ ${#pids[@]} -ge $MAX_CONCURRENT ]]; then + sleep 1 + fi + done + + # Start heartbeat check in background + ( + if check_agent_heartbeat "$agent_name" "$agent_name"; then + echo "SUCCESS:$agent_name" + else + echo "FAILED:$agent_name" + fi + ) & + + pids+=($!) + ((processed++)) + done <<< "$agents" + + # Wait for all background processes + for pid in "${pids[@]}"; do + if wait "$pid"; then + ((successful++)) + else + ((failed++)) + fi + done + + log "INFO" "Heartbeat check completed: $processed processed, $successful successful, $failed failed" + + # Return appropriate exit code + if [[ $failed -gt 0 ]]; then + exit 1 + else + exit 0 + fi +} + +# Handle script arguments +case "${1:-}" in + --help|-h) + echo "Mission Control Agent Heartbeat Script" + echo "" + echo "Usage: $0 [agent_name]" + echo "" + echo "Options:" + echo " agent_name Check specific agent only" + echo " --help, -h Show this help message" + echo "" + echo "Environment variables:" + echo " MISSION_CONTROL_URL Mission Control base URL (default: http://localhost:3005)" + echo "" + exit 0 + ;; + *) + main "$@" + ;; +esac diff --git a/scripts/notification-daemon.sh b/scripts/notification-daemon.sh new file mode 100644 index 0000000000000000000000000000000000000000..b44794a2220b87e19a785d0a118430bc249a9bf8 --- /dev/null +++ b/scripts/notification-daemon.sh @@ -0,0 +1,347 @@ +#!/bin/bash + +# Mission Control Phase 3: Notification Delivery Daemon +# Polls undelivered notifications and sends them to agent sessions via OpenClaw +# +# Usage: +# scripts/notification-daemon.sh [options] +# +# Options: +# --agent AGENT_NAME Only deliver notifications to specific agent +# --limit N Max notifications to process per batch (default: 50) +# --dry-run Test mode - don't actually deliver notifications +# --daemon Run in daemon mode (continuous polling) +# --interval SECONDS Polling interval in daemon mode (default: 60) + +set -e + +# Configuration +MISSION_CONTROL_URL="${MISSION_CONTROL_URL:-http://localhost:3005}" +LOG_DIR="${LOG_DIR:-$HOME/.mission-control/logs}" +LOG_FILE="$LOG_DIR/notification-daemon-$(date +%Y-%m-%d).log" +PID_FILE="/tmp/notification-daemon.pid" +DEFAULT_INTERVAL=60 +DEFAULT_LIMIT=50 + +# Command line options +AGENT_FILTER="" +LIMIT=$DEFAULT_LIMIT +DRY_RUN=false +DAEMON_MODE=false +INTERVAL=$DEFAULT_INTERVAL + +# Ensure log directory exists +mkdir -p "$LOG_DIR" + +# Logging function +log() { + local level="$1" + shift + echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$level] $*" | tee -a "$LOG_FILE" +} + +# Check if Mission Control is running +check_mission_control() { + if ! curl -s "$MISSION_CONTROL_URL/api/status" > /dev/null 2>&1; then + log "ERROR" "Mission Control not accessible at $MISSION_CONTROL_URL" + return 1 + fi + return 0 +} + +# Process and deliver notifications +deliver_notifications() { + log "INFO" "Starting notification delivery batch" + + # Build API request + local api_payload="{\"limit\": $LIMIT" + + if [[ -n "$AGENT_FILTER" ]]; then + api_payload+=", \"agent_filter\": \"$AGENT_FILTER\"" + fi + + if [[ "$DRY_RUN" == "true" ]]; then + api_payload+=", \"dry_run\": true" + fi + + api_payload+="}" + + # Call notification delivery endpoint + local response + response=$(curl -s -w "HTTP_STATUS:%{http_code}" \ + -X POST \ + -H "Content-Type: application/json" \ + -d "$api_payload" \ + "$MISSION_CONTROL_URL/api/notifications/deliver" 2>/dev/null) + + local http_code + http_code=$(echo "$response" | grep -o "HTTP_STATUS:[0-9]*" | cut -d: -f2) + local body + body=$(echo "$response" | sed 's/HTTP_STATUS:[0-9]*$//') + + if [[ "$http_code" != "200" ]]; then + log "ERROR" "Notification delivery failed: HTTP $http_code" + log "ERROR" "Response: $body" + return 1 + fi + + # Parse results + local status delivered errors total_processed + status=$(echo "$body" | jq -r '.status // "unknown"' 2>/dev/null || echo "parse_error") + delivered=$(echo "$body" | jq -r '.delivered // 0' 2>/dev/null || echo "0") + errors=$(echo "$body" | jq -r '.errors // 0' 2>/dev/null || echo "0") + total_processed=$(echo "$body" | jq -r '.total_processed // 0' 2>/dev/null || echo "0") + + if [[ "$status" == "success" ]]; then + if [[ "$total_processed" -gt 0 ]]; then + log "INFO" "Batch completed: $total_processed processed, $delivered delivered, $errors failed" + + # Log detailed errors if any + if [[ "$errors" -gt 0 ]]; then + local error_details + error_details=$(echo "$body" | jq -r '.error_details[]? | "- \(.recipient): \(.error)"' 2>/dev/null || echo "") + if [[ -n "$error_details" ]]; then + log "WARN" "Error details:" + echo "$error_details" | while read -r line; do + log "WARN" " $line" + done + fi + fi + else + log "INFO" "No notifications to deliver" + fi + + return 0 + else + log "ERROR" "Unexpected delivery response: $status" + return 1 + fi +} + +# Get delivery statistics +get_delivery_stats() { + local stats_url="$MISSION_CONTROL_URL/api/notifications/deliver" + + if [[ -n "$AGENT_FILTER" ]]; then + stats_url+="?agent=$AGENT_FILTER" + fi + + local response + response=$(curl -s "$stats_url" 2>/dev/null) + + if [[ $? -eq 0 ]]; then + echo "$response" | jq -r ' + "Delivery Statistics:", + " Total notifications: \(.statistics.total)", + " Delivered: \(.statistics.delivered)", + " Undelivered: \(.statistics.undelivered)", + " Delivery rate: \(.statistics.delivery_rate)%", + "", + "Agents with pending notifications:", + (.agents_with_pending[] | " \(.recipient): \(.pending_count) pending\(if .session_key then "" else " (no session key)" end)") + ' 2>/dev/null || echo "Failed to parse statistics" + else + echo "Failed to fetch delivery statistics" + fi +} + +# Daemon mode signal handlers +cleanup() { + log "INFO" "Received shutdown signal, stopping daemon" + rm -f "$PID_FILE" + exit 0 +} + +# Check if daemon is already running +check_daemon() { + if [[ -f "$PID_FILE" ]]; then + local old_pid + old_pid=$(cat "$PID_FILE" 2>/dev/null || echo "") + + if [[ -n "$old_pid" ]] && kill -0 "$old_pid" 2>/dev/null; then + log "ERROR" "Notification daemon already running with PID $old_pid" + exit 1 + else + log "WARN" "Stale PID file found, removing" + rm -f "$PID_FILE" + fi + fi +} + +# Run in daemon mode +run_daemon() { + log "INFO" "Starting notification daemon (PID: $$)" + + # Check if already running + check_daemon + + # Write PID file + echo $$ > "$PID_FILE" + + # Set up signal handlers + trap cleanup SIGTERM SIGINT SIGQUIT + + # Main daemon loop + while true; do + if ! check_mission_control; then + log "WARN" "Mission Control not accessible, sleeping $INTERVAL seconds" + sleep "$INTERVAL" + continue + fi + + # Process notifications + if deliver_notifications; then + log "DEBUG" "Delivery batch completed successfully" + else + log "WARN" "Delivery batch had errors" + fi + + # Sleep until next cycle + sleep "$INTERVAL" + done +} + +# Parse command line arguments +parse_args() { + while [[ $# -gt 0 ]]; do + case $1 in + --agent) + AGENT_FILTER="$2" + shift 2 + ;; + --limit) + LIMIT="$2" + shift 2 + ;; + --dry-run) + DRY_RUN=true + shift + ;; + --daemon) + DAEMON_MODE=true + shift + ;; + --interval) + INTERVAL="$2" + shift 2 + ;; + --stats) + get_delivery_stats + exit 0 + ;; + --stop) + if [[ -f "$PID_FILE" ]]; then + local pid + pid=$(cat "$PID_FILE" 2>/dev/null || echo "") + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + kill -TERM "$pid" + log "INFO" "Sent stop signal to daemon (PID: $pid)" + exit 0 + else + log "WARN" "No running daemon found" + rm -f "$PID_FILE" + exit 1 + fi + else + log "WARN" "No daemon PID file found" + exit 1 + fi + ;; + --help|-h) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" >&2 + show_help + exit 1 + ;; + esac + done +} + +# Show help +show_help() { + cat << 'EOF' +Mission Control Notification Delivery Daemon + +Usage: notification-daemon.sh [options] + +Options: + --agent AGENT_NAME Only deliver notifications to specific agent + --limit N Max notifications to process per batch (default: 50) + --dry-run Test mode - don't actually deliver notifications + --daemon Run in daemon mode (continuous polling) + --interval SECONDS Polling interval in daemon mode (default: 60) + --stats Show delivery statistics and exit + --stop Stop running daemon + --help, -h Show this help message + +Examples: + # Single batch delivery + ./notification-daemon.sh + + # Dry run to test + ./notification-daemon.sh --dry-run + + # Deliver only to specific agent + ./notification-daemon.sh --agent "coordinator" + + # Run as daemon + ./notification-daemon.sh --daemon --interval 30 + + # Show statistics + ./notification-daemon.sh --stats + + # Stop daemon + ./notification-daemon.sh --stop + +Environment variables: + MISSION_CONTROL_URL Mission Control base URL (default: http://localhost:3005) + +Log files: + $LOG_DIR/notification-daemon-YYYY-MM-DD.log +EOF +} + +# Validate arguments +validate_args() { + if ! [[ "$LIMIT" =~ ^[1-9][0-9]*$ ]]; then + log "ERROR" "Invalid limit: $LIMIT (must be positive integer)" + exit 1 + fi + + if ! [[ "$INTERVAL" =~ ^[1-9][0-9]*$ ]]; then + log "ERROR" "Invalid interval: $INTERVAL (must be positive integer)" + exit 1 + fi +} + +# Main execution +main() { + parse_args "$@" + validate_args + + if [[ "$DAEMON_MODE" == "true" ]]; then + run_daemon + else + # Single run mode + log "INFO" "Starting single notification delivery run" + + if ! check_mission_control; then + log "ERROR" "Aborting: Mission Control not accessible" + exit 1 + fi + + if deliver_notifications; then + log "INFO" "Notification delivery completed successfully" + exit 0 + else + log "ERROR" "Notification delivery failed" + exit 1 + fi + fi +} + +# Run main function +main "$@" diff --git a/src/app/api/activities/route.ts b/src/app/api/activities/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..378c8147d55592a3a64929624ccdb61d83a47eaf --- /dev/null +++ b/src/app/api/activities/route.ts @@ -0,0 +1,217 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, Activity } from '@/lib/db'; + +/** + * GET /api/activities - Get activity stream or stats + * Query params: type, actor, entity_type, limit, offset, since, hours (for stats) + */ +export async function GET(request: NextRequest) { + try { + const { searchParams, pathname } = new URL(request.url); + + // Route to stats endpoint if requested + if (pathname.endsWith('/stats') || searchParams.has('stats')) { + return handleStatsRequest(request); + } + + // Default activities endpoint + return handleActivitiesRequest(request); + } catch (error) { + console.error('GET /api/activities error:', error); + return NextResponse.json({ error: 'Failed to process request' }, { status: 500 }); + } +} + +/** + * Handle regular activities request + */ +async function handleActivitiesRequest(request: NextRequest) { + try { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + + // Parse query parameters + const type = searchParams.get('type'); + const actor = searchParams.get('actor'); + const entity_type = searchParams.get('entity_type'); + const limit = parseInt(searchParams.get('limit') || '50'); + const offset = parseInt(searchParams.get('offset') || '0'); + const since = searchParams.get('since'); // Unix timestamp for real-time updates + + // Build dynamic query + let query = 'SELECT * FROM activities WHERE 1=1'; + const params: any[] = []; + + if (type) { + query += ' AND type = ?'; + params.push(type); + } + + if (actor) { + query += ' AND actor = ?'; + params.push(actor); + } + + if (entity_type) { + query += ' AND entity_type = ?'; + params.push(entity_type); + } + + if (since) { + query += ' AND created_at > ?'; + params.push(parseInt(since)); + } + + query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; + params.push(limit, offset); + + const stmt = db.prepare(query); + const activities = stmt.all(...params) as Activity[]; + + // Parse JSON data field and enhance with related entity data + const enhancedActivities = activities.map(activity => { + let entityDetails = null; + + try { + // Fetch related entity details based on entity_type + switch (activity.entity_type) { + case 'task': + const task = db.prepare('SELECT id, title, status FROM tasks WHERE id = ?').get(activity.entity_id) as any; + if (task) { + entityDetails = { type: 'task', ...task }; + } + break; + + case 'agent': + const agent = db.prepare('SELECT id, name, role, status FROM agents WHERE id = ?').get(activity.entity_id) as any; + if (agent) { + entityDetails = { type: 'agent', ...agent }; + } + break; + + case 'comment': + const comment = db.prepare(` + SELECT c.id, c.content, c.task_id, t.title as task_title + FROM comments c + LEFT JOIN tasks t ON c.task_id = t.id + WHERE c.id = ? + `).get(activity.entity_id) as any; + if (comment) { + entityDetails = { + type: 'comment', + ...comment, + content_preview: comment.content?.substring(0, 100) || '' + }; + } + break; + } + } catch (error) { + // If entity lookup fails, continue without entity details + console.warn(`Failed to fetch entity details for activity ${activity.id}:`, error); + } + + return { + ...activity, + data: activity.data ? JSON.parse(activity.data) : null, + entity: entityDetails + }; + }); + + // Get total count for pagination + let countQuery = 'SELECT COUNT(*) as total FROM activities WHERE 1=1'; + const countParams: any[] = []; + + if (type) { + countQuery += ' AND type = ?'; + countParams.push(type); + } + + if (actor) { + countQuery += ' AND actor = ?'; + countParams.push(actor); + } + + if (entity_type) { + countQuery += ' AND entity_type = ?'; + countParams.push(entity_type); + } + + if (since) { + countQuery += ' AND created_at > ?'; + countParams.push(parseInt(since)); + } + + const countResult = db.prepare(countQuery).get(...countParams) as { total: number }; + + return NextResponse.json({ + activities: enhancedActivities, + total: countResult.total, + hasMore: offset + activities.length < countResult.total + }); + } catch (error) { + console.error('GET /api/activities (activities) error:', error); + return NextResponse.json({ error: 'Failed to fetch activities' }, { status: 500 }); + } +} + +/** + * Handle stats request + */ +async function handleStatsRequest(request: NextRequest) { + try { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + + // Parse timeframe parameter (defaults to 24 hours) + const hours = parseInt(searchParams.get('hours') || '24'); + const since = Math.floor(Date.now() / 1000) - (hours * 3600); + + // Get activity counts by type + const activityStats = db.prepare(` + SELECT + type, + COUNT(*) as count + FROM activities + WHERE created_at > ? + GROUP BY type + ORDER BY count DESC + `).all(since) as { type: string; count: number }[]; + + // Get most active actors + const activeActors = db.prepare(` + SELECT + actor, + COUNT(*) as activity_count + FROM activities + WHERE created_at > ? + GROUP BY actor + ORDER BY activity_count DESC + LIMIT 10 + `).all(since) as { actor: string; activity_count: number }[]; + + // Get activity timeline (hourly buckets) + const timeline = db.prepare(` + SELECT + (created_at / 3600) * 3600 as hour_bucket, + COUNT(*) as count + FROM activities + WHERE created_at > ? + GROUP BY hour_bucket + ORDER BY hour_bucket ASC + `).all(since) as { hour_bucket: number; count: number }[]; + + return NextResponse.json({ + timeframe: `${hours} hours`, + activityByType: activityStats, + topActors: activeActors, + timeline: timeline.map(item => ({ + timestamp: item.hour_bucket, + count: item.count, + hour: new Date(item.hour_bucket * 1000).toISOString() + })) + }); + } catch (error) { + console.error('GET /api/activities (stats) error:', error); + return NextResponse.json({ error: 'Failed to fetch activity stats' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/agents/[id]/heartbeat/route.ts b/src/app/api/agents/[id]/heartbeat/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..4dc120fc1bc757528f8f1295acfc6d21019abdfe --- /dev/null +++ b/src/app/api/agents/[id]/heartbeat/route.ts @@ -0,0 +1,179 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, db_helpers } from '@/lib/db'; +import { requireRole } from '@/lib/auth'; + +/** + * GET /api/agents/[id]/heartbeat - Agent heartbeat check + * + * Checks for: + * - @mentions in recent comments + * - Assigned tasks + * - Recent activity feed items + * + * Returns work items or "HEARTBEAT_OK" if nothing to do + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const db = getDatabase(); + const resolvedParams = await params; + const agentId = resolvedParams.id; + + // Get agent by ID or name + let agent; + if (isNaN(Number(agentId))) { + // Lookup by name + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(agentId); + } else { + // Lookup by ID + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(agentId)); + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); + } + + const workItems: any[] = []; + const now = Math.floor(Date.now() / 1000); + const fourHoursAgo = now - (4 * 60 * 60); // Check last 4 hours + + // 1. Check for @mentions in recent comments + const mentions = db.prepare(` + SELECT c.*, t.title as task_title + FROM comments c + JOIN tasks t ON c.task_id = t.id + WHERE c.mentions LIKE ? + AND c.created_at > ? + ORDER BY c.created_at DESC + LIMIT 10 + `).all(`%"${agent.name}"%`, fourHoursAgo); + + if (mentions.length > 0) { + workItems.push({ + type: 'mentions', + count: mentions.length, + items: mentions.map((m: any) => ({ + id: m.id, + task_title: m.task_title, + author: m.author, + content: m.content.substring(0, 100) + '...', + created_at: m.created_at + })) + }); + } + + // 2. Check for assigned tasks + const assignedTasks = db.prepare(` + SELECT * FROM tasks + WHERE assigned_to = ? + AND status IN ('assigned', 'in_progress') + ORDER BY priority DESC, created_at ASC + LIMIT 10 + `).all(agent.name); + + if (assignedTasks.length > 0) { + workItems.push({ + type: 'assigned_tasks', + count: assignedTasks.length, + items: assignedTasks.map((t: any) => ({ + id: t.id, + title: t.title, + status: t.status, + priority: t.priority, + due_date: t.due_date + })) + }); + } + + // 3. Check for unread notifications + const notifications = db_helpers.getUnreadNotifications(agent.name); + + if (notifications.length > 0) { + workItems.push({ + type: 'notifications', + count: notifications.length, + items: notifications.slice(0, 5).map(n => ({ + id: n.id, + type: n.type, + title: n.title, + message: n.message, + created_at: n.created_at + })) + }); + } + + // 4. Check for urgent activities that might need attention + const urgentActivities = db.prepare(` + SELECT * FROM activities + WHERE type IN ('task_created', 'task_assigned', 'high_priority_alert') + AND created_at > ? + AND description LIKE ? + ORDER BY created_at DESC + LIMIT 5 + `).all(fourHoursAgo, `%${agent.name}%`); + + if (urgentActivities.length > 0) { + workItems.push({ + type: 'urgent_activities', + count: urgentActivities.length, + items: urgentActivities.map((a: any) => ({ + id: a.id, + type: a.type, + description: a.description, + created_at: a.created_at + })) + }); + } + + // Update agent last_seen and status to show heartbeat activity + db_helpers.updateAgentStatus(agent.name, 'idle', 'Heartbeat check'); + + // Log heartbeat activity + db_helpers.logActivity( + 'agent_heartbeat', + 'agent', + agent.id, + agent.name, + `Heartbeat check completed - ${workItems.length > 0 ? `${workItems.length} work items found` : 'no work items'}`, + { workItemsCount: workItems.length, workItemTypes: workItems.map(w => w.type) } + ); + + if (workItems.length === 0) { + return NextResponse.json({ + status: 'HEARTBEAT_OK', + agent: agent.name, + checked_at: now, + message: 'No work items found' + }); + } + + return NextResponse.json({ + status: 'WORK_ITEMS_FOUND', + agent: agent.name, + checked_at: now, + work_items: workItems, + total_items: workItems.reduce((sum, item) => sum + item.count, 0) + }); + + } catch (error) { + console.error('GET /api/agents/[id]/heartbeat error:', error); + return NextResponse.json({ error: 'Failed to perform heartbeat check' }, { status: 500 }); + } +} + +/** + * POST /api/agents/[id]/heartbeat - Manual heartbeat trigger + * Allows manual heartbeat checks from UI or scripts + */ +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + // Reuse GET logic for manual triggers + return GET(request, { params }); +} \ No newline at end of file diff --git a/src/app/api/agents/[id]/memory/route.ts b/src/app/api/agents/[id]/memory/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..1047e748169d27d09d0f7beb92909fd7926aaff7 --- /dev/null +++ b/src/app/api/agents/[id]/memory/route.ts @@ -0,0 +1,213 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, db_helpers } from '@/lib/db'; +import { requireRole } from '@/lib/auth'; + +/** + * GET /api/agents/[id]/memory - Get agent's working memory + * + * Working memory is stored as WORKING.md content in the database + * Each agent has their own working memory space for temporary notes + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'viewer'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const resolvedParams = await params; + const agentId = resolvedParams.id; + + // Get agent by ID or name + let agent; + if (isNaN(Number(agentId))) { + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(agentId); + } else { + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(agentId)); + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); + } + + // Check if agent has a working_memory column, if not create it + const columns = db.prepare("PRAGMA table_info(agents)").all(); + const hasWorkingMemory = columns.some((col: any) => col.name === 'working_memory'); + + if (!hasWorkingMemory) { + // Add working_memory column to agents table + db.exec("ALTER TABLE agents ADD COLUMN working_memory TEXT DEFAULT ''"); + } + + // Get working memory content + const memoryStmt = db.prepare(`SELECT working_memory FROM agents WHERE ${isNaN(Number(agentId)) ? 'name' : 'id'} = ?`); + const result = memoryStmt.get(agentId) as any; + + const workingMemory = result?.working_memory || ''; + + return NextResponse.json({ + agent: { + id: agent.id, + name: agent.name, + role: agent.role + }, + working_memory: workingMemory, + updated_at: agent.updated_at, + size: workingMemory.length + }); + } catch (error) { + console.error('GET /api/agents/[id]/memory error:', error); + return NextResponse.json({ error: 'Failed to fetch working memory' }, { status: 500 }); + } +} + +/** + * PUT /api/agents/[id]/memory - Update agent's working memory + */ +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const resolvedParams = await params; + const agentId = resolvedParams.id; + const body = await request.json(); + const { working_memory, append } = body; + + // Get agent by ID or name + let agent; + if (isNaN(Number(agentId))) { + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(agentId); + } else { + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(agentId)); + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); + } + + // Check if agent has a working_memory column, if not create it + const columns = db.prepare("PRAGMA table_info(agents)").all(); + const hasWorkingMemory = columns.some((col: any) => col.name === 'working_memory'); + + if (!hasWorkingMemory) { + db.exec("ALTER TABLE agents ADD COLUMN working_memory TEXT DEFAULT ''"); + } + + let newContent = working_memory || ''; + + // Handle append mode + if (append) { + const currentStmt = db.prepare(`SELECT working_memory FROM agents WHERE ${isNaN(Number(agentId)) ? 'name' : 'id'} = ?`); + const current = currentStmt.get(agentId) as any; + const currentContent = current?.working_memory || ''; + + // Add timestamp and append + const timestamp = new Date().toISOString(); + newContent = currentContent + (currentContent ? '\n\n' : '') + + `## ${timestamp}\n${working_memory}`; + } + + const now = Math.floor(Date.now() / 1000); + + // Update working memory + const updateStmt = db.prepare(` + UPDATE agents + SET working_memory = ?, updated_at = ? + WHERE ${isNaN(Number(agentId)) ? 'name' : 'id'} = ? + `); + + updateStmt.run(newContent, now, agentId); + + // Log activity + db_helpers.logActivity( + 'agent_memory_updated', + 'agent', + agent.id, + agent.name, + `Working memory ${append ? 'appended' : 'updated'} for agent ${agent.name}`, + { + content_length: newContent.length, + append_mode: append || false, + timestamp: now + } + ); + + return NextResponse.json({ + success: true, + message: `Working memory ${append ? 'appended' : 'updated'} for ${agent.name}`, + working_memory: newContent, + updated_at: now, + size: newContent.length + }); + } catch (error) { + console.error('PUT /api/agents/[id]/memory error:', error); + return NextResponse.json({ error: 'Failed to update working memory' }, { status: 500 }); + } +} + +/** + * DELETE /api/agents/[id]/memory - Clear agent's working memory + */ +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const resolvedParams = await params; + const agentId = resolvedParams.id; + + // Get agent by ID or name + let agent; + if (isNaN(Number(agentId))) { + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(agentId); + } else { + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(agentId)); + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); + } + + const now = Math.floor(Date.now() / 1000); + + // Clear working memory + const updateStmt = db.prepare(` + UPDATE agents + SET working_memory = '', updated_at = ? + WHERE ${isNaN(Number(agentId)) ? 'name' : 'id'} = ? + `); + + updateStmt.run(now, agentId); + + // Log activity + db_helpers.logActivity( + 'agent_memory_cleared', + 'agent', + agent.id, + agent.name, + `Working memory cleared for agent ${agent.name}`, + { timestamp: now } + ); + + return NextResponse.json({ + success: true, + message: `Working memory cleared for ${agent.name}`, + working_memory: '', + updated_at: now + }); + } catch (error) { + console.error('DELETE /api/agents/[id]/memory error:', error); + return NextResponse.json({ error: 'Failed to clear working memory' }, { status: 500 }); + } +} diff --git a/src/app/api/agents/[id]/route.ts b/src/app/api/agents/[id]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..ee80987e36dac97dbd09114198d4b83ce7b14fdc --- /dev/null +++ b/src/app/api/agents/[id]/route.ts @@ -0,0 +1,206 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers, logAuditEvent } from '@/lib/db' +import { getUserFromRequest, requireRole } from '@/lib/auth' +import { writeAgentToConfig } from '@/lib/agent-sync' +import { eventBus } from '@/lib/event-bus' + +/** + * GET /api/agents/[id] - Get a single agent by ID or name + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const db = getDatabase() + const { id } = await params + + let agent + if (isNaN(Number(id))) { + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(id) + } else { + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(id)) + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }) + } + + const parsed = { + ...(agent as any), + config: (agent as any).config ? JSON.parse((agent as any).config) : {}, + } + + return NextResponse.json({ agent: parsed }) + } catch (error) { + console.error('GET /api/agents/[id] error:', error) + return NextResponse.json({ error: 'Failed to fetch agent' }, { status: 500 }) + } +} + +/** + * PUT /api/agents/[id] - Update agent config with optional gateway write-back + * + * Body: { + * role?: string + * gateway_config?: object - OpenClaw agent config fields to update + * write_to_gateway?: boolean - If true, also write to openclaw.json + * } + */ +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const { id } = await params + const body = await request.json() + const { role, gateway_config, write_to_gateway } = body + + let agent + if (isNaN(Number(id))) { + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(id) as any + } else { + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(id)) as any + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }) + } + + const now = Math.floor(Date.now() / 1000) + const existingConfig = agent.config ? JSON.parse(agent.config) : {} + + // Merge gateway_config into existing config + let newConfig = existingConfig + if (gateway_config && typeof gateway_config === 'object') { + newConfig = { ...existingConfig, ...gateway_config } + } + + // Build update + const fields: string[] = ['updated_at = ?'] + const values: any[] = [now] + + if (role !== undefined) { + fields.push('role = ?') + values.push(role) + } + + if (gateway_config) { + fields.push('config = ?') + values.push(JSON.stringify(newConfig)) + } + + values.push(agent.id) + db.prepare(`UPDATE agents SET ${fields.join(', ')} WHERE id = ?`).run(...values) + + // Write back to openclaw.json if requested + if (write_to_gateway && gateway_config) { + try { + const openclawId = existingConfig.openclawId || agent.name.toLowerCase().replace(/\s+/g, '-') + + // Build the config to write back (full OpenClaw format) + const writeBack: any = { id: openclawId } + if (gateway_config.model) writeBack.model = gateway_config.model + if (gateway_config.identity) writeBack.identity = gateway_config.identity + if (gateway_config.sandbox) writeBack.sandbox = gateway_config.sandbox + if (gateway_config.tools) writeBack.tools = gateway_config.tools + if (gateway_config.subagents) writeBack.subagents = gateway_config.subagents + if (gateway_config.memorySearch) writeBack.memorySearch = gateway_config.memorySearch + + await writeAgentToConfig(writeBack) + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'agent_config_writeback', + actor: auth.user.username, + actor_id: auth.user.id, + target_type: 'agent', + target_id: agent.id, + detail: { agent_name: agent.name, openclaw_id: openclawId, fields: Object.keys(gateway_config) }, + ip_address: ipAddress, + }) + } catch (err: any) { + // Config update succeeded in DB but gateway write failed + return NextResponse.json({ + warning: `Agent updated in MC but gateway write failed: ${err.message}`, + agent: { ...agent, config: newConfig, role: role || agent.role, updated_at: now }, + }) + } + } + + // Log activity + db_helpers.logActivity( + 'agent_config_updated', + 'agent', + agent.id, + auth.user.username, + `Config updated for agent ${agent.name}${write_to_gateway ? ' (+ gateway)' : ''}`, + { fields: Object.keys(gateway_config || {}), write_to_gateway } + ) + + // Broadcast update + eventBus.broadcast('agent.updated', { + id: agent.id, + name: agent.name, + config: newConfig, + updated_at: now, + }) + + return NextResponse.json({ + success: true, + agent: { ...agent, config: newConfig, role: role || agent.role, updated_at: now }, + }) + } catch (error: any) { + console.error('PUT /api/agents/[id] error:', error) + return NextResponse.json({ error: error.message || 'Failed to update agent' }, { status: 500 }) + } +} + +/** + * DELETE /api/agents/[id] - Delete an agent + */ +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const { id } = await params + + let agent + if (isNaN(Number(id))) { + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(id) as any + } else { + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(id)) as any + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }) + } + + db.prepare('DELETE FROM agents WHERE id = ?').run(agent.id) + + db_helpers.logActivity( + 'agent_deleted', + 'agent', + agent.id, + auth.user.username, + `Deleted agent: ${agent.name}`, + { name: agent.name, role: agent.role } + ) + + eventBus.broadcast('agent.deleted', { id: agent.id, name: agent.name }) + + return NextResponse.json({ success: true, deleted: agent.name }) + } catch (error) { + console.error('DELETE /api/agents/[id] error:', error) + return NextResponse.json({ error: 'Failed to delete agent' }, { status: 500 }) + } +} diff --git a/src/app/api/agents/[id]/soul/route.ts b/src/app/api/agents/[id]/soul/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..2d84f671cc92d870faa2f4a9f2260b01b8f2caa2 --- /dev/null +++ b/src/app/api/agents/[id]/soul/route.ts @@ -0,0 +1,229 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, db_helpers } from '@/lib/db'; +import { readFileSync, existsSync, readdirSync } from 'fs'; +import { join } from 'path'; +import { config } from '@/lib/config'; +import { resolveWithin } from '@/lib/paths'; +import { getUserFromRequest, requireRole } from '@/lib/auth'; + +/** + * GET /api/agents/[id]/soul - Get agent's SOUL content + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const db = getDatabase(); + const resolvedParams = await params; + const agentId = resolvedParams.id; + + // Get agent by ID or name + let agent; + if (isNaN(Number(agentId))) { + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(agentId); + } else { + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(agentId)); + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); + } + + const templatesPath = config.soulTemplatesDir; + let availableTemplates: string[] = []; + + try { + if (templatesPath && existsSync(templatesPath)) { + const files = readdirSync(templatesPath); + availableTemplates = files + .filter(file => file.endsWith('.md')) + .map(file => file.replace('.md', '')); + } + } catch (error) { + console.warn('Could not read soul templates directory:', error); + } + + return NextResponse.json({ + agent: { + id: agent.id, + name: agent.name, + role: agent.role + }, + soul_content: agent.soul_content || '', + available_templates: availableTemplates, + updated_at: agent.updated_at + }); + } catch (error) { + console.error('GET /api/agents/[id]/soul error:', error); + return NextResponse.json({ error: 'Failed to fetch SOUL content' }, { status: 500 }); + } +} + +/** + * PUT /api/agents/[id]/soul - Update agent's SOUL content + */ +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const resolvedParams = await params; + const agentId = resolvedParams.id; + const body = await request.json(); + const { soul_content, template_name } = body; + + // Get agent by ID or name + let agent; + if (isNaN(Number(agentId))) { + agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(agentId); + } else { + agent = db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(agentId)); + } + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); + } + + let newSoulContent = soul_content; + + // If template_name is provided, load from template + if (template_name) { + if (!config.soulTemplatesDir) { + return NextResponse.json({ error: 'Templates directory not configured' }, { status: 500 }); + } + let templatePath: string; + try { + templatePath = resolveWithin(config.soulTemplatesDir, `${template_name}.md`); + } catch (pathError) { + return NextResponse.json({ error: 'Invalid template name' }, { status: 400 }); + } + + try { + if (existsSync(templatePath)) { + const templateContent = readFileSync(templatePath, 'utf8'); + // Replace placeholders with agent info + newSoulContent = templateContent + .replace(/{{AGENT_NAME}}/g, agent.name) + .replace(/{{AGENT_ROLE}}/g, agent.role) + .replace(/{{TIMESTAMP}}/g, new Date().toISOString()); + } else { + return NextResponse.json({ error: 'Template not found' }, { status: 404 }); + } + } catch (error) { + console.error('Error loading soul template:', error); + return NextResponse.json({ error: 'Failed to load template' }, { status: 500 }); + } + } + + const now = Math.floor(Date.now() / 1000); + + // Update SOUL content + const updateStmt = db.prepare(` + UPDATE agents + SET soul_content = ?, updated_at = ? + WHERE ${isNaN(Number(agentId)) ? 'name' : 'id'} = ? + `); + + updateStmt.run(newSoulContent, now, agentId); + + // Log activity + db_helpers.logActivity( + 'agent_soul_updated', + 'agent', + agent.id, + getUserFromRequest(request)?.username || 'system', + `SOUL content updated for agent ${agent.name}${template_name ? ` using template: ${template_name}` : ''}`, + { + template_used: template_name || null, + content_length: newSoulContent ? newSoulContent.length : 0, + previous_content_length: agent.soul_content ? agent.soul_content.length : 0 + } + ); + + return NextResponse.json({ + success: true, + message: `SOUL content updated for ${agent.name}`, + soul_content: newSoulContent, + updated_at: now + }); + } catch (error) { + console.error('PUT /api/agents/[id]/soul error:', error); + return NextResponse.json({ error: 'Failed to update SOUL content' }, { status: 500 }); + } +} + +/** + * GET /api/agents/[id]/soul/templates - Get available SOUL templates + * Also handles loading specific template content + */ +export async function PATCH( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { searchParams } = new URL(request.url); + const templateName = searchParams.get('template'); + + const templatesPath = config.soulTemplatesDir; + + if (!templatesPath || !existsSync(templatesPath)) { + return NextResponse.json({ + templates: [], + message: 'Templates directory not found' + }); + } + + if (templateName) { + // Get specific template content + let templatePath: string; + try { + templatePath = resolveWithin(templatesPath, `${templateName}.md`); + } catch (pathError) { + return NextResponse.json({ error: 'Invalid template name' }, { status: 400 }); + } + + if (!existsSync(templatePath)) { + return NextResponse.json({ error: 'Template not found' }, { status: 404 }); + } + + const templateContent = readFileSync(templatePath, 'utf8'); + + return NextResponse.json({ + template_name: templateName, + content: templateContent + }); + } + + // List all available templates + const files = readdirSync(templatesPath); + const templates = files + .filter(file => file.endsWith('.md')) + .map(file => { + const name = file.replace('.md', ''); + const templatePath = join(templatesPath, file); + const content = readFileSync(templatePath, 'utf8'); + + // Extract first line as description + const firstLine = content.split('\n')[0]; + const description = firstLine.startsWith('#') + ? firstLine.replace(/^#+\s*/, '') + : `${name} template`; + + return { + name, + description, + size: content.length + }; + }); + + return NextResponse.json({ templates }); + } catch (error) { + console.error('PATCH /api/agents/[id]/soul error:', error); + return NextResponse.json({ error: 'Failed to fetch templates' }, { status: 500 }); + } +} diff --git a/src/app/api/agents/[id]/wake/route.ts b/src/app/api/agents/[id]/wake/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..75ae6aeb2fba6f511d222ee8985e4847c0c635de --- /dev/null +++ b/src/app/api/agents/[id]/wake/route.ts @@ -0,0 +1,63 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers } from '@/lib/db' +import { runOpenClaw } from '@/lib/command' +import { requireRole } from '@/lib/auth' + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const resolvedParams = await params + const agentId = resolvedParams.id + const body = await request.json().catch(() => ({})) + const customMessage = + typeof body?.message === 'string' ? body.message.trim() : '' + + const db = getDatabase() + const agent: any = isNaN(Number(agentId)) + ? db.prepare('SELECT * FROM agents WHERE name = ?').get(agentId) + : db.prepare('SELECT * FROM agents WHERE id = ?').get(Number(agentId)) + + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }) + } + + if (!agent.session_key) { + return NextResponse.json( + { error: 'Agent has no session key configured' }, + { status: 400 } + ) + } + + const message = + customMessage || + `Wake up check-in for ${agent.name}. Please review assigned tasks and notifications.` + + const { stdout, stderr } = await runOpenClaw( + ['gateway', 'sessions_send', '--session', agent.session_key, '--message', message], + { timeoutMs: 10000 } + ) + + if (stderr && stderr.includes('error')) { + return NextResponse.json( + { error: stderr.trim() || 'Failed to wake agent' }, + { status: 500 } + ) + } + + db_helpers.updateAgentStatus(agent.name, 'idle', 'Manual wake') + + return NextResponse.json({ + success: true, + session_key: agent.session_key, + stdout: stdout.trim() + }) + } catch (error) { + console.error('POST /api/agents/[id]/wake error:', error) + return NextResponse.json({ error: 'Failed to wake agent' }, { status: 500 }) + } +} diff --git a/src/app/api/agents/comms/route.ts b/src/app/api/agents/comms/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..29b0c01e14c83393aae9ad8204c649508510501c --- /dev/null +++ b/src/app/api/agents/comms/route.ts @@ -0,0 +1,155 @@ +import { NextRequest, NextResponse } from "next/server" +import { getDatabase, Message } from "@/lib/db" + +/** + * GET /api/agents/comms - Inter-agent communication stats and timeline + * Query params: limit, offset, since, agent + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + + const limit = parseInt(searchParams.get("limit") || "100") + const offset = parseInt(searchParams.get("offset") || "0") + const since = searchParams.get("since") + const agent = searchParams.get("agent") + + // Filter out human/system messages - only agent-to-agent + const humanNames = ["human", "system", "operator"] + const humanPlaceholders = humanNames.map(() => "?").join(",") + + // 1. Get inter-agent messages + let messagesQuery = ` + SELECT * FROM messages + WHERE to_agent IS NOT NULL + AND from_agent NOT IN (${humanPlaceholders}) + AND to_agent NOT IN (${humanPlaceholders}) + ` + const messagesParams: any[] = [...humanNames, ...humanNames] + + if (since) { + messagesQuery += " AND created_at > ?" + messagesParams.push(parseInt(since)) + } + if (agent) { + messagesQuery += " AND (from_agent = ? OR to_agent = ?)" + messagesParams.push(agent, agent) + } + + // Deterministic chronological ordering prevents visual jumps in UI + messagesQuery += " ORDER BY created_at ASC, id ASC LIMIT ? OFFSET ?" + messagesParams.push(limit, offset) + + const messages = db.prepare(messagesQuery).all(...messagesParams) as Message[] + + // 2. Communication graph edges + let graphQuery = ` + SELECT + from_agent, to_agent, + COUNT(*) as message_count, + MAX(created_at) as last_message_at + FROM messages + WHERE to_agent IS NOT NULL + AND from_agent NOT IN (${humanPlaceholders}) + AND to_agent NOT IN (${humanPlaceholders}) + ` + const graphParams: any[] = [...humanNames, ...humanNames] + if (since) { + graphQuery += " AND created_at > ?" + graphParams.push(parseInt(since)) + } + graphQuery += " GROUP BY from_agent, to_agent ORDER BY message_count DESC" + + const edges = db.prepare(graphQuery).all(...graphParams) + + // 3. Per-agent sent/received stats + const statsQuery = ` + SELECT agent, SUM(sent) as sent, SUM(received) as received FROM ( + SELECT from_agent as agent, COUNT(*) as sent, 0 as received + FROM messages WHERE to_agent IS NOT NULL + AND from_agent NOT IN (${humanPlaceholders}) + AND to_agent NOT IN (${humanPlaceholders}) + GROUP BY from_agent + UNION ALL + SELECT to_agent as agent, 0 as sent, COUNT(*) as received + FROM messages WHERE to_agent IS NOT NULL + AND from_agent NOT IN (${humanPlaceholders}) + AND to_agent NOT IN (${humanPlaceholders}) + GROUP BY to_agent + ) GROUP BY agent ORDER BY (sent + received) DESC + ` + const statsParams = [...humanNames, ...humanNames, ...humanNames, ...humanNames] + const agentStats = db.prepare(statsQuery).all(...statsParams) + + // 4. Total count + let countQuery = ` + SELECT COUNT(*) as total FROM messages + WHERE to_agent IS NOT NULL + AND from_agent NOT IN (${humanPlaceholders}) + AND to_agent NOT IN (${humanPlaceholders}) + ` + const countParams: any[] = [...humanNames, ...humanNames] + if (since) { + countQuery += " AND created_at > ?" + countParams.push(parseInt(since)) + } + if (agent) { + countQuery += " AND (from_agent = ? OR to_agent = ?)" + countParams.push(agent, agent) + } + const { total } = db.prepare(countQuery).get(...countParams) as { total: number } + + let seededCountQuery = ` + SELECT COUNT(*) as seeded FROM messages + WHERE to_agent IS NOT NULL + AND from_agent NOT IN (${humanPlaceholders}) + AND to_agent NOT IN (${humanPlaceholders}) + AND conversation_id LIKE ? + ` + const seededParams: any[] = [...humanNames, ...humanNames, "conv-multi-%"] + if (since) { + seededCountQuery += " AND created_at > ?" + seededParams.push(parseInt(since)) + } + if (agent) { + seededCountQuery += " AND (from_agent = ? OR to_agent = ?)" + seededParams.push(agent, agent) + } + const { seeded } = db.prepare(seededCountQuery).get(...seededParams) as { seeded: number } + + const seededCount = seeded || 0 + const liveCount = Math.max(0, total - seededCount) + const source = + total === 0 ? "empty" : + liveCount === 0 ? "seeded" : + seededCount === 0 ? "live" : + "mixed" + + const parsed = messages.map((msg) => { + let parsedMetadata: any = null + if (msg.metadata) { + try { + parsedMetadata = JSON.parse(msg.metadata) + } catch { + // Keep endpoint resilient even if one legacy row has bad metadata + parsedMetadata = null + } + } + return { + ...msg, + metadata: parsedMetadata, + } + }) + + return NextResponse.json({ + messages: parsed, + total, + graph: { edges, agentStats }, + source: { mode: source, seededCount, liveCount }, + }) + } catch (error) { + console.error("GET /api/agents/comms error:", error) + return NextResponse.json({ error: "Failed to fetch agent communications" }, { status: 500 }) + } +} diff --git a/src/app/api/agents/message/route.ts b/src/app/api/agents/message/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..d2541b1595e627d36df666e53a30ff2b93ff34a1 --- /dev/null +++ b/src/app/api/agents/message/route.ts @@ -0,0 +1,70 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers } from '@/lib/db' +import { runOpenClaw } from '@/lib/command' +import { requireRole } from '@/lib/auth' + +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const body = await request.json() + const from = (body.from || 'system') as string + const to = (body.to || '').trim() + const message = (body.message || '').trim() + + if (!to || !message) { + return NextResponse.json( + { error: 'Both "to" and "message" are required' }, + { status: 400 } + ) + } + + const db = getDatabase() + const agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(to) as any + if (!agent) { + return NextResponse.json({ error: 'Recipient agent not found' }, { status: 404 }) + } + if (!agent.session_key) { + return NextResponse.json( + { error: 'Recipient agent has no session key configured' }, + { status: 400 } + ) + } + + await runOpenClaw( + [ + 'gateway', + 'sessions_send', + '--session', + agent.session_key, + '--message', + `Message from ${from}: ${message}` + ], + { timeoutMs: 10000 } + ) + + db_helpers.createNotification( + to, + 'message', + 'Direct Message', + `${from}: ${message.substring(0, 200)}${message.length > 200 ? '...' : ''}`, + 'agent', + agent.id + ) + + db_helpers.logActivity( + 'agent_message', + 'agent', + agent.id, + from, + `Sent message to ${to}`, + { to } + ) + + return NextResponse.json({ success: true }) + } catch (error) { + console.error('POST /api/agents/message error:', error) + return NextResponse.json({ error: 'Failed to send message' }, { status: 500 }) + } +} diff --git a/src/app/api/agents/route.ts b/src/app/api/agents/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..a2d24a88dcec656a1b1e710416179d1ae95a537d --- /dev/null +++ b/src/app/api/agents/route.ts @@ -0,0 +1,331 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, Agent, db_helpers } from '@/lib/db'; +import { eventBus } from '@/lib/event-bus'; +import { getTemplate, buildAgentConfig } from '@/lib/agent-templates'; +import { writeAgentToConfig } from '@/lib/agent-sync'; +import { logAuditEvent } from '@/lib/db'; +import { getUserFromRequest, requireRole } from '@/lib/auth'; + +/** + * GET /api/agents - List all agents with optional filtering + * Query params: status, role, limit, offset + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + + // Parse query parameters + const status = searchParams.get('status'); + const role = searchParams.get('role'); + const limit = parseInt(searchParams.get('limit') || '50'); + const offset = parseInt(searchParams.get('offset') || '0'); + + // Build dynamic query + let query = 'SELECT * FROM agents WHERE 1=1'; + const params: any[] = []; + + if (status) { + query += ' AND status = ?'; + params.push(status); + } + + if (role) { + query += ' AND role = ?'; + params.push(role); + } + + query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; + params.push(limit, offset); + + const stmt = db.prepare(query); + const agents = stmt.all(...params) as Agent[]; + + // Parse JSON config field + const agentsWithParsedData = agents.map(agent => ({ + ...agent, + config: agent.config ? JSON.parse(agent.config) : {} + })); + + // Get task counts for each agent + const agentsWithStats = agentsWithParsedData.map(agent => { + const taskCountStmt = db.prepare(` + SELECT + COUNT(*) as total, + SUM(CASE WHEN status = 'assigned' THEN 1 ELSE 0 END) as assigned, + SUM(CASE WHEN status = 'in_progress' THEN 1 ELSE 0 END) as in_progress, + SUM(CASE WHEN status = 'done' THEN 1 ELSE 0 END) as completed + FROM tasks + WHERE assigned_to = ? + `); + + const taskStats = taskCountStmt.get(agent.name) as any; + + return { + ...agent, + taskStats: { + total: taskStats.total || 0, + assigned: taskStats.assigned || 0, + in_progress: taskStats.in_progress || 0, + completed: taskStats.completed || 0 + } + }; + }); + + return NextResponse.json({ + agents: agentsWithStats, + total: agents.length + }); + } catch (error) { + console.error('GET /api/agents error:', error); + return NextResponse.json({ error: 'Failed to fetch agents' }, { status: 500 }); + } +} + +/** + * POST /api/agents - Create a new agent + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const body = await request.json(); + + const { + name, + role, + session_key, + soul_content, + status = 'offline', + config = {}, + template, + gateway_config, + write_to_gateway + } = body; + + // Resolve template if specified + let finalRole = role; + let finalConfig = config; + if (template) { + const tpl = getTemplate(template); + if (tpl) { + const builtConfig = buildAgentConfig(tpl, gateway_config || {}); + finalConfig = { ...builtConfig, ...config }; + if (!finalRole) finalRole = tpl.config.identity?.theme || tpl.type; + } + } else if (gateway_config) { + finalConfig = { ...config, ...gateway_config }; + } + + if (!name || !finalRole) { + return NextResponse.json({ error: 'Name and role are required' }, { status: 400 }); + } + + // Check if agent name already exists + const existingAgent = db.prepare('SELECT id FROM agents WHERE name = ?').get(name); + if (existingAgent) { + return NextResponse.json({ error: 'Agent name already exists' }, { status: 409 }); + } + + const now = Math.floor(Date.now() / 1000); + + const stmt = db.prepare(` + INSERT INTO agents ( + name, role, session_key, soul_content, status, + created_at, updated_at, config + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `); + + const result = stmt.run( + name, + finalRole, + session_key, + soul_content, + status, + now, + now, + JSON.stringify(finalConfig) + ); + + const agentId = result.lastInsertRowid as number; + + // Log activity + db_helpers.logActivity( + 'agent_created', + 'agent', + agentId, + getUserFromRequest(request)?.username || 'system', + `Created agent: ${name} (${finalRole})${template ? ` from template: ${template}` : ''}`, + { + name, + role: finalRole, + status, + session_key, + template: template || null + } + ); + + // Fetch the created agent + const createdAgent = db.prepare('SELECT * FROM agents WHERE id = ?').get(agentId) as Agent; + const parsedAgent = { + ...createdAgent, + config: JSON.parse(createdAgent.config || '{}'), + taskStats: { total: 0, assigned: 0, in_progress: 0, completed: 0 } + }; + + // Broadcast to SSE clients + eventBus.broadcast('agent.created', parsedAgent); + + // Write to gateway config if requested + if (write_to_gateway && finalConfig) { + try { + const openclawId = (name || 'agent').toLowerCase().replace(/\s+/g, '-'); + await writeAgentToConfig({ + id: openclawId, + name, + ...(finalConfig.model && { model: finalConfig.model }), + ...(finalConfig.identity && { identity: finalConfig.identity }), + ...(finalConfig.sandbox && { sandbox: finalConfig.sandbox }), + ...(finalConfig.tools && { tools: finalConfig.tools }), + ...(finalConfig.subagents && { subagents: finalConfig.subagents }), + ...(finalConfig.memorySearch && { memorySearch: finalConfig.memorySearch }), + }); + + const ipAddress = request.headers.get('x-forwarded-for') || 'unknown'; + logAuditEvent({ + action: 'agent_gateway_create', + actor: getUserFromRequest(request)?.username || 'system', + target_type: 'agent', + target_id: agentId as number, + detail: { name, openclaw_id: openclawId, template: template || null }, + ip_address: ipAddress, + }); + } catch (gwErr: any) { + console.error('Gateway write-back failed:', gwErr); + return NextResponse.json({ + agent: parsedAgent, + warning: `Agent created in MC but gateway write failed: ${gwErr.message}` + }, { status: 201 }); + } + } + + return NextResponse.json({ agent: parsedAgent }, { status: 201 }); + } catch (error) { + console.error('POST /api/agents error:', error); + return NextResponse.json({ error: 'Failed to create agent' }, { status: 500 }); + } +} + +/** + * PUT /api/agents - Update agent status (bulk operation for status updates) + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const body = await request.json(); + + // Handle single agent update or bulk updates + if (body.name) { + // Single agent update + const { name, status, last_activity, config, session_key, soul_content, role } = body; + + const agent = db.prepare('SELECT * FROM agents WHERE name = ?').get(name) as Agent; + if (!agent) { + return NextResponse.json({ error: 'Agent not found' }, { status: 404 }); + } + + const now = Math.floor(Date.now() / 1000); + + // Build dynamic update query + const fieldsToUpdate = []; + const params: any[] = []; + + if (status !== undefined) { + fieldsToUpdate.push('status = ?'); + params.push(status); + + fieldsToUpdate.push('last_seen = ?'); + params.push(now); + } + + if (last_activity !== undefined) { + fieldsToUpdate.push('last_activity = ?'); + params.push(last_activity); + } + + if (config !== undefined) { + fieldsToUpdate.push('config = ?'); + params.push(JSON.stringify(config)); + } + + if (session_key !== undefined) { + fieldsToUpdate.push('session_key = ?'); + params.push(session_key); + } + + if (soul_content !== undefined) { + fieldsToUpdate.push('soul_content = ?'); + params.push(soul_content); + } + + if (role !== undefined) { + fieldsToUpdate.push('role = ?'); + params.push(role); + } + + fieldsToUpdate.push('updated_at = ?'); + params.push(now); + params.push(name); + + if (fieldsToUpdate.length === 1) { // Only updated_at + return NextResponse.json({ error: 'No fields to update' }, { status: 400 }); + } + + const stmt = db.prepare(` + UPDATE agents + SET ${fieldsToUpdate.join(', ')} + WHERE name = ? + `); + + stmt.run(...params); + + // Log status change if status was updated + if (status !== undefined && status !== agent.status) { + db_helpers.logActivity( + 'agent_status_change', + 'agent', + agent.id, + name, + `Agent status changed from ${agent.status} to ${status}`, + { + oldStatus: agent.status, + newStatus: status, + last_activity + } + ); + } + + // Broadcast update to SSE clients + eventBus.broadcast('agent.updated', { + id: agent.id, + name, + ...(status !== undefined && { status }), + ...(last_activity !== undefined && { last_activity }), + ...(role !== undefined && { role }), + updated_at: now, + }); + + return NextResponse.json({ success: true }); + } else { + return NextResponse.json({ error: 'Agent name is required' }, { status: 400 }); + } + } catch (error) { + console.error('PUT /api/agents error:', error); + return NextResponse.json({ error: 'Failed to update agent' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/agents/sync/route.ts b/src/app/api/agents/sync/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..2bc4d8d73ff09741dccdf22a6bcc93b7d6a46027 --- /dev/null +++ b/src/app/api/agents/sync/route.ts @@ -0,0 +1,42 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { syncAgentsFromConfig, previewSyncDiff } from '@/lib/agent-sync' + +/** + * POST /api/agents/sync - Trigger agent config sync from openclaw.json + * Requires admin role. + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const result = await syncAgentsFromConfig(auth.user.username) + + if (result.error) { + return NextResponse.json({ error: result.error }, { status: 500 }) + } + + return NextResponse.json(result) + } catch (error: any) { + console.error('POST /api/agents/sync error:', error) + return NextResponse.json({ error: error.message || 'Sync failed' }, { status: 500 }) + } +} + +/** + * GET /api/agents/sync - Preview diff between openclaw.json and MC + * Shows what would change without writing. + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const diff = await previewSyncDiff() + return NextResponse.json(diff) + } catch (error: any) { + console.error('GET /api/agents/sync error:', error) + return NextResponse.json({ error: error.message || 'Preview failed' }, { status: 500 }) + } +} diff --git a/src/app/api/alerts/route.ts b/src/app/api/alerts/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..18b6a5814db5a4b1fd09900fd340ec3a2852b5f0 --- /dev/null +++ b/src/app/api/alerts/route.ts @@ -0,0 +1,311 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase } from '@/lib/db' + +interface AlertRule { + id: number + name: string + description: string | null + enabled: number + entity_type: string + condition_field: string + condition_operator: string + condition_value: string + action_type: string + action_config: string + cooldown_minutes: number + last_triggered_at: number | null + trigger_count: number + created_by: string + created_at: number + updated_at: number +} + +/** + * GET /api/alerts - List all alert rules + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'viewer') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + try { + const rules = db.prepare('SELECT * FROM alert_rules ORDER BY created_at DESC').all() as AlertRule[] + return NextResponse.json({ rules }) + } catch { + return NextResponse.json({ rules: [] }) + } +} + +/** + * POST /api/alerts - Create a new alert rule or evaluate rules + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + const body = await request.json() + + // Evaluate all enabled rules + if (body.action === 'evaluate') { + return evaluateRules(db) + } + + // Create new rule + const { name, description, entity_type, condition_field, condition_operator, condition_value, action_type, action_config, cooldown_minutes } = body + + if (!name || !entity_type || !condition_field || !condition_operator || !condition_value) { + return NextResponse.json({ error: 'Missing required fields: name, entity_type, condition_field, condition_operator, condition_value' }, { status: 400 }) + } + + const validEntities = ['agent', 'task', 'session', 'activity'] + if (!validEntities.includes(entity_type)) { + return NextResponse.json({ error: `entity_type must be one of: ${validEntities.join(', ')}` }, { status: 400 }) + } + + const validOperators = ['equals', 'not_equals', 'greater_than', 'less_than', 'contains', 'count_above', 'count_below', 'age_minutes_above'] + if (!validOperators.includes(condition_operator)) { + return NextResponse.json({ error: `condition_operator must be one of: ${validOperators.join(', ')}` }, { status: 400 }) + } + + try { + const result = db.prepare(` + INSERT INTO alert_rules (name, description, entity_type, condition_field, condition_operator, condition_value, action_type, action_config, cooldown_minutes, created_by) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `).run( + name, + description || null, + entity_type, + condition_field, + condition_operator, + condition_value, + action_type || 'notification', + JSON.stringify(action_config || {}), + cooldown_minutes || 60, + auth.user?.username || 'system' + ) + + // Audit log + try { + db.prepare('INSERT INTO audit_log (action, actor, detail) VALUES (?, ?, ?)').run( + 'alert_rule_created', + auth.user?.username || 'system', + `Created alert rule: ${name}` + ) + } catch { /* audit table might not exist */ } + + const rule = db.prepare('SELECT * FROM alert_rules WHERE id = ?').get(result.lastInsertRowid) as AlertRule + return NextResponse.json({ rule }, { status: 201 }) + } catch (err: any) { + return NextResponse.json({ error: err.message || 'Failed to create rule' }, { status: 500 }) + } +} + +/** + * PUT /api/alerts - Update an alert rule + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + const body = await request.json() + const { id, ...updates } = body + + if (!id) return NextResponse.json({ error: 'id is required' }, { status: 400 }) + + const existing = db.prepare('SELECT * FROM alert_rules WHERE id = ?').get(id) as AlertRule | undefined + if (!existing) return NextResponse.json({ error: 'Rule not found' }, { status: 404 }) + + const allowed = ['name', 'description', 'enabled', 'entity_type', 'condition_field', 'condition_operator', 'condition_value', 'action_type', 'action_config', 'cooldown_minutes'] + const sets: string[] = [] + const values: any[] = [] + + for (const key of allowed) { + if (key in updates) { + sets.push(`${key} = ?`) + values.push(key === 'action_config' ? JSON.stringify(updates[key]) : updates[key]) + } + } + + if (sets.length === 0) return NextResponse.json({ error: 'No valid fields to update' }, { status: 400 }) + + sets.push('updated_at = (unixepoch())') + values.push(id) + + db.prepare(`UPDATE alert_rules SET ${sets.join(', ')} WHERE id = ?`).run(...values) + + const updated = db.prepare('SELECT * FROM alert_rules WHERE id = ?').get(id) as AlertRule + return NextResponse.json({ rule: updated }) +} + +/** + * DELETE /api/alerts - Delete an alert rule + */ +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + const body = await request.json() + const { id } = body + + if (!id) return NextResponse.json({ error: 'id is required' }, { status: 400 }) + + const result = db.prepare('DELETE FROM alert_rules WHERE id = ?').run(id) + + try { + db.prepare('INSERT INTO audit_log (action, actor, detail) VALUES (?, ?, ?)').run( + 'alert_rule_deleted', + auth.user?.username || 'system', + `Deleted alert rule #${id}` + ) + } catch { /* audit table might not exist */ } + + return NextResponse.json({ deleted: result.changes > 0 }) +} + +/** + * Evaluate all enabled alert rules against current data + */ +function evaluateRules(db: ReturnType) { + let rules: AlertRule[] + try { + rules = db.prepare('SELECT * FROM alert_rules WHERE enabled = 1').all() as AlertRule[] + } catch { + return NextResponse.json({ evaluated: 0, triggered: 0, results: [] }) + } + + const now = Math.floor(Date.now() / 1000) + const results: { rule_id: number; rule_name: string; triggered: boolean; reason?: string }[] = [] + + for (const rule of rules) { + // Check cooldown + if (rule.last_triggered_at && (now - rule.last_triggered_at) < rule.cooldown_minutes * 60) { + results.push({ rule_id: rule.id, rule_name: rule.name, triggered: false, reason: 'In cooldown' }) + continue + } + + const triggered = evaluateRule(db, rule, now) + results.push({ rule_id: rule.id, rule_name: rule.name, triggered, reason: triggered ? 'Condition met' : 'Condition not met' }) + + if (triggered) { + // Update trigger tracking + db.prepare('UPDATE alert_rules SET last_triggered_at = ?, trigger_count = trigger_count + 1 WHERE id = ?').run(now, rule.id) + + // Create notification + try { + const config = JSON.parse(rule.action_config || '{}') + const recipient = config.recipient || 'system' + db.prepare(` + INSERT INTO notifications (recipient, type, title, message, source_type, source_id) + VALUES (?, 'alert', ?, ?, 'alert_rule', ?) + `).run(recipient, `Alert: ${rule.name}`, rule.description || `Rule "${rule.name}" triggered`, rule.id) + } catch { /* notification creation failed */ } + } + } + + const triggered = results.filter(r => r.triggered).length + return NextResponse.json({ evaluated: rules.length, triggered, results }) +} + +function evaluateRule(db: ReturnType, rule: AlertRule, now: number): boolean { + try { + switch (rule.entity_type) { + case 'agent': return evaluateAgentRule(db, rule, now) + case 'task': return evaluateTaskRule(db, rule, now) + case 'session': return evaluateSessionRule(db, rule, now) + case 'activity': return evaluateActivityRule(db, rule, now) + default: return false + } + } catch { + return false + } +} + +function evaluateAgentRule(db: ReturnType, rule: AlertRule, now: number): boolean { + const { condition_field, condition_operator, condition_value } = rule + + if (condition_operator === 'count_above' || condition_operator === 'count_below') { + const count = (db.prepare(`SELECT COUNT(*) as c FROM agents WHERE ${safeColumn('agents', condition_field)} = ?`).get(condition_value) as any)?.c || 0 + return condition_operator === 'count_above' ? count > parseInt(condition_value) : count < parseInt(condition_value) + } + + if (condition_operator === 'age_minutes_above') { + // Check agents where field value is older than N minutes (e.g., last_seen) + const threshold = now - parseInt(condition_value) * 60 + const count = (db.prepare(`SELECT COUNT(*) as c FROM agents WHERE status != 'offline' AND ${safeColumn('agents', condition_field)} < ?`).get(threshold) as any)?.c || 0 + return count > 0 + } + + const agents = db.prepare(`SELECT ${safeColumn('agents', condition_field)} as val FROM agents WHERE status != 'offline'`).all() as any[] + return agents.some(a => compareValue(a.val, condition_operator, condition_value)) +} + +function evaluateTaskRule(db: ReturnType, rule: AlertRule, _now: number): boolean { + const { condition_field, condition_operator, condition_value } = rule + + if (condition_operator === 'count_above') { + const count = (db.prepare(`SELECT COUNT(*) as c FROM tasks WHERE ${safeColumn('tasks', condition_field)} = ?`).get(condition_value) as any)?.c || 0 + return count > parseInt(condition_value) + } + + if (condition_operator === 'count_below') { + const count = (db.prepare(`SELECT COUNT(*) as c FROM tasks`).get() as any)?.c || 0 + return count < parseInt(condition_value) + } + + const tasks = db.prepare(`SELECT ${safeColumn('tasks', condition_field)} as val FROM tasks`).all() as any[] + return tasks.some(t => compareValue(t.val, condition_operator, condition_value)) +} + +function evaluateSessionRule(db: ReturnType, rule: AlertRule, _now: number): boolean { + // Session data comes from the gateway, not the DB, so we check the agents table for session info + const { condition_operator, condition_value } = rule + + if (condition_operator === 'count_above') { + const count = (db.prepare(`SELECT COUNT(*) as c FROM agents WHERE status = 'busy'`).get() as any)?.c || 0 + return count > parseInt(condition_value) + } + + return false +} + +function evaluateActivityRule(db: ReturnType, rule: AlertRule, now: number): boolean { + const { condition_field, condition_operator, condition_value } = rule + + if (condition_operator === 'count_above') { + // Count activities in the last hour + const hourAgo = now - 3600 + const count = (db.prepare(`SELECT COUNT(*) as c FROM activities WHERE created_at > ? AND ${safeColumn('activities', condition_field)} = ?`).get(hourAgo, condition_value) as any)?.c || 0 + return count > parseInt(condition_value) + } + + return false +} + +function compareValue(actual: any, operator: string, expected: string): boolean { + if (actual == null) return false + const strActual = String(actual) + switch (operator) { + case 'equals': return strActual === expected + case 'not_equals': return strActual !== expected + case 'greater_than': return Number(actual) > Number(expected) + case 'less_than': return Number(actual) < Number(expected) + case 'contains': return strActual.toLowerCase().includes(expected.toLowerCase()) + default: return false + } +} + +// Whitelist of columns per table to prevent SQL injection +const SAFE_COLUMNS: Record> = { + agents: new Set(['status', 'role', 'name', 'last_seen', 'last_activity']), + tasks: new Set(['status', 'priority', 'assigned_to', 'title']), + activities: new Set(['type', 'actor', 'entity_type']), +} + +function safeColumn(table: string, column: string): string { + if (SAFE_COLUMNS[table]?.has(column)) return column + return 'id' // fallback to safe column +} diff --git a/src/app/api/audit/route.ts b/src/app/api/audit/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..9a8d7976ecd567b416b671f05bf0ee9d930f57d5 --- /dev/null +++ b/src/app/api/audit/route.ts @@ -0,0 +1,66 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase } from '@/lib/db' + +function safeParseJson(str: string): any { + try { return JSON.parse(str) } catch { return str } +} + +/** + * GET /api/audit - Query audit log (admin only) + * Query params: action, actor, limit, offset, since, until + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') + const actor = searchParams.get('actor') + const limit = Math.min(parseInt(searchParams.get('limit') || '100'), 500) + const offset = parseInt(searchParams.get('offset') || '0') + const since = searchParams.get('since') + const until = searchParams.get('until') + + const conditions: string[] = [] + const params: any[] = [] + + if (action) { + conditions.push('action = ?') + params.push(action) + } + if (actor) { + conditions.push('actor = ?') + params.push(actor) + } + if (since) { + conditions.push('created_at >= ?') + params.push(parseInt(since)) + } + if (until) { + conditions.push('created_at <= ?') + params.push(parseInt(until)) + } + + const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '' + + const db = getDatabase() + + const total = (db.prepare(`SELECT COUNT(*) as count FROM audit_log ${where}`).get(...params) as any).count + + const rows = db.prepare(` + SELECT * FROM audit_log ${where} + ORDER BY created_at DESC + LIMIT ? OFFSET ? + `).all(...params, limit, offset) + + return NextResponse.json({ + events: rows.map((row: any) => ({ + ...row, + detail: row.detail ? safeParseJson(row.detail) : null, + })), + total, + limit, + offset, + }) +} diff --git a/src/app/api/auth/access-requests/route.ts b/src/app/api/auth/access-requests/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..51b7fa894ee04cf83bb4fb3832b75e8fbab3e8ef --- /dev/null +++ b/src/app/api/auth/access-requests/route.ts @@ -0,0 +1,147 @@ +import { randomBytes } from 'crypto' +import { NextRequest, NextResponse } from 'next/server' +import { createUser, getUserFromRequest } from '@/lib/auth' +import { getDatabase, logAuditEvent } from '@/lib/db' + +function makeUsernameFromEmail(email: string): string { + const base = email.split('@')[0].replace(/[^a-z0-9._-]/gi, '').toLowerCase() || 'user' + return base.slice(0, 28) +} + +function ensureUniqueUsername(base: string): string { + const db = getDatabase() + let candidate = base + let i = 0 + while (db.prepare('SELECT 1 FROM users WHERE username = ?').get(candidate)) { + i += 1 + candidate = `${base.slice(0, 24)}-${i}` + } + return candidate +} + +export async function GET(request: NextRequest) { + const user = getUserFromRequest(request) + if (!user || user.role !== 'admin') { + return NextResponse.json({ error: 'Admin access required' }, { status: 403 }) + } + + const db = getDatabase() + db.exec(` + CREATE TABLE IF NOT EXISTS access_requests ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + provider TEXT NOT NULL DEFAULT 'google', + email TEXT NOT NULL, + provider_user_id TEXT, + display_name TEXT, + avatar_url TEXT, + status TEXT NOT NULL DEFAULT 'pending', + requested_at INTEGER NOT NULL DEFAULT (unixepoch()), + last_attempt_at INTEGER NOT NULL DEFAULT (unixepoch()), + attempt_count INTEGER NOT NULL DEFAULT 1, + reviewed_by TEXT, + reviewed_at INTEGER, + review_note TEXT, + approved_user_id INTEGER + ) + `) + + const status = String(request.nextUrl.searchParams.get('status') || 'all') + const rows = status === 'all' + ? db.prepare("SELECT * FROM access_requests ORDER BY status = 'pending' DESC, last_attempt_at DESC, id DESC").all() + : db.prepare('SELECT * FROM access_requests WHERE status = ? ORDER BY last_attempt_at DESC, id DESC').all(status) + + return NextResponse.json({ requests: rows }) +} + +export async function POST(request: NextRequest) { + const admin = getUserFromRequest(request) + if (!admin || admin.role !== 'admin') { + return NextResponse.json({ error: 'Admin access required' }, { status: 403 }) + } + + const db = getDatabase() + const body = await request.json().catch(() => ({})) + const requestId = Number(body?.request_id) + const action = String(body?.action || '') + const role = String(body?.role || 'viewer') as 'admin' | 'operator' | 'viewer' + const note = body?.note ? String(body.note) : null + + if (!Number.isInteger(requestId) || requestId <= 0) { + return NextResponse.json({ error: 'request_id is required' }, { status: 400 }) + } + if (!['approve', 'reject'].includes(action)) { + return NextResponse.json({ error: 'action must be approve or reject' }, { status: 400 }) + } + if (!['admin', 'operator', 'viewer'].includes(role)) { + return NextResponse.json({ error: 'Invalid role' }, { status: 400 }) + } + + const reqRow = db.prepare('SELECT * FROM access_requests WHERE id = ?').get(requestId) as any + if (!reqRow) return NextResponse.json({ error: 'Request not found' }, { status: 404 }) + + if (action === 'reject') { + db.prepare(` + UPDATE access_requests + SET status = 'rejected', reviewed_by = ?, reviewed_at = (unixepoch()), review_note = ? + WHERE id = ? + `).run(admin.username, note, requestId) + + logAuditEvent({ + action: 'access_request_rejected', + actor: admin.username, + actor_id: admin.id, + detail: { request_id: requestId, email: reqRow.email, note }, + }) + + return NextResponse.json({ ok: true }) + } + + const email = String(reqRow.email || '').toLowerCase() + const providerUserId = reqRow.provider_user_id ? String(reqRow.provider_user_id) : null + const displayName = String(reqRow.display_name || email.split('@')[0] || 'Google User') + const avatarUrl = reqRow.avatar_url ? String(reqRow.avatar_url) : null + + const user = db.transaction(() => { + const existing = db.prepare('SELECT * FROM users WHERE lower(email) = ? OR (provider = ? AND provider_user_id = ?) ORDER BY id ASC LIMIT 1').get(email, 'google', providerUserId || '') as any + + let userId: number + if (existing) { + db.prepare(` + UPDATE users + SET provider = 'google', provider_user_id = ?, email = ?, avatar_url = COALESCE(?, avatar_url), is_approved = 1, role = ?, approved_by = ?, approved_at = (unixepoch()), updated_at = (unixepoch()) + WHERE id = ? + `).run(providerUserId, email, avatarUrl, role, admin.username, existing.id) + userId = Number(existing.id) + } else { + const username = ensureUniqueUsername(makeUsernameFromEmail(email)) + const randomPwd = randomBytes(24).toString('hex') + const created = createUser(username, randomPwd, displayName, role, { + provider: 'google', + provider_user_id: providerUserId, + email, + avatar_url: avatarUrl, + is_approved: 1, + approved_by: admin.username, + approved_at: Math.floor(Date.now() / 1000), + }) + userId = created.id + } + + db.prepare(` + UPDATE access_requests + SET status = 'approved', reviewed_by = ?, reviewed_at = (unixepoch()), review_note = ?, approved_user_id = ? + WHERE id = ? + `).run(admin.username, note, userId, requestId) + + return db.prepare('SELECT id, username, display_name, role, provider, email, avatar_url, is_approved FROM users WHERE id = ?').get(userId) + })() as any + + logAuditEvent({ + action: 'access_request_approved', + actor: admin.username, + actor_id: admin.id, + detail: { request_id: requestId, email, role, user_id: user?.id, note }, + }) + + return NextResponse.json({ ok: true, user }) +} diff --git a/src/app/api/auth/google/route.ts b/src/app/api/auth/google/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..c5740570f32d6108174ef3b807bef12ec7c0ccbb --- /dev/null +++ b/src/app/api/auth/google/route.ts @@ -0,0 +1,103 @@ +import { randomBytes } from 'crypto' +import { NextResponse } from 'next/server' +import { createSession } from '@/lib/auth' +import { getDatabase, logAuditEvent } from '@/lib/db' +import { verifyGoogleIdToken } from '@/lib/google-auth' +import { getMcSessionCookieOptions } from '@/lib/session-cookie' + +function upsertAccessRequest(input: { + email: string + providerUserId: string + displayName: string + avatarUrl?: string +}) { + const db = getDatabase() + db.prepare(` + INSERT INTO access_requests (provider, email, provider_user_id, display_name, avatar_url, status, attempt_count, requested_at, last_attempt_at) + VALUES ('google', ?, ?, ?, ?, 'pending', 1, (unixepoch()), (unixepoch())) + ON CONFLICT(email, provider) DO UPDATE SET + provider_user_id = excluded.provider_user_id, + display_name = excluded.display_name, + avatar_url = excluded.avatar_url, + status = 'pending', + attempt_count = access_requests.attempt_count + 1, + last_attempt_at = (unixepoch()) + `).run(input.email.toLowerCase(), input.providerUserId, input.displayName, input.avatarUrl || null) +} + +export async function POST(request: Request) { + try { + const body = await request.json().catch(() => ({})) + const credential = String(body?.credential || '') + const profile = await verifyGoogleIdToken(credential) + + const db = getDatabase() + const email = String(profile.email || '').toLowerCase().trim() + const sub = String(profile.sub || '').trim() + const displayName = String(profile.name || email.split('@')[0] || 'Google User').trim() + const avatar = profile.picture ? String(profile.picture) : null + + const row = db.prepare(` + SELECT id, username, display_name, role, provider, email, avatar_url, is_approved, created_at, updated_at, last_login_at + FROM users + WHERE (provider = 'google' AND provider_user_id = ?) OR lower(email) = ? + ORDER BY id ASC + LIMIT 1 + `).get(sub, email) as any + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + const userAgent = request.headers.get('user-agent') || undefined + + if (!row || Number(row.is_approved ?? 1) !== 1) { + upsertAccessRequest({ + email, + providerUserId: sub, + displayName, + avatarUrl: avatar || undefined, + }) + + logAuditEvent({ + action: 'google_login_pending_approval', + actor: email, + detail: { email, sub }, + ip_address: ipAddress, + user_agent: userAgent, + }) + + return NextResponse.json( + { error: 'Access request pending admin approval', code: 'PENDING_APPROVAL' }, + { status: 403 } + ) + } + + db.prepare(` + UPDATE users + SET provider = 'google', provider_user_id = ?, email = ?, avatar_url = COALESCE(?, avatar_url), updated_at = (unixepoch()) + WHERE id = ? + `).run(sub, email, avatar, row.id) + + const { token, expiresAt } = createSession(row.id, ipAddress, userAgent) + + logAuditEvent({ action: 'login_google', actor: row.username, actor_id: row.id, ip_address: ipAddress, user_agent: userAgent }) + + const response = NextResponse.json({ + user: { + id: row.id, + username: row.username, + display_name: row.display_name, + role: row.role, + provider: 'google', + email, + avatar_url: avatar, + }, + }) + + response.cookies.set('mc-session', token, { + ...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000) }), + }) + + return response + } catch (error: any) { + return NextResponse.json({ error: error?.message || 'Google login failed' }, { status: 400 }) + } +} diff --git a/src/app/api/auth/login/route.ts b/src/app/api/auth/login/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..f55ff35f96bc895061aae545a1696e7a657b38b2 --- /dev/null +++ b/src/app/api/auth/login/route.ts @@ -0,0 +1,48 @@ +import { NextResponse } from 'next/server' +import { authenticateUser, createSession } from '@/lib/auth' +import { logAuditEvent } from '@/lib/db' +import { getMcSessionCookieOptions } from '@/lib/session-cookie' + +export async function POST(request: Request) { + try { + const { username, password } = await request.json() + + if (!username || !password) { + return NextResponse.json({ error: 'Username and password are required' }, { status: 400 }) + } + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + const userAgent = request.headers.get('user-agent') || undefined + + const user = authenticateUser(username, password) + if (!user) { + logAuditEvent({ action: 'login_failed', actor: username, ip_address: ipAddress, user_agent: userAgent }) + return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 }) + } + + const { token, expiresAt } = createSession(user.id, ipAddress, userAgent) + + logAuditEvent({ action: 'login', actor: user.username, actor_id: user.id, ip_address: ipAddress, user_agent: userAgent }) + + const response = NextResponse.json({ + user: { + id: user.id, + username: user.username, + display_name: user.display_name, + role: user.role, + provider: user.provider || 'local', + email: user.email || null, + avatar_url: user.avatar_url || null, + }, + }) + + response.cookies.set('mc-session', token, { + ...getMcSessionCookieOptions({ maxAgeSeconds: expiresAt - Math.floor(Date.now() / 1000) }), + }) + + return response + } catch (error) { + console.error('Login error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/src/app/api/auth/logout/route.ts b/src/app/api/auth/logout/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..1eff1a8dd354e90e90b6237a63015e710e6b7815 --- /dev/null +++ b/src/app/api/auth/logout/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from 'next/server' +import { destroySession, getUserFromRequest } from '@/lib/auth' +import { logAuditEvent } from '@/lib/db' +import { getMcSessionCookieOptions } from '@/lib/session-cookie' + +export async function POST(request: Request) { + const user = getUserFromRequest(request) + const cookieHeader = request.headers.get('cookie') || '' + const match = cookieHeader.match(/(?:^|;\s*)mc-session=([^;]*)/) + const token = match ? decodeURIComponent(match[1]) : null + + if (token) { + destroySession(token) + } + + if (user) { + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ action: 'logout', actor: user.username, actor_id: user.id, ip_address: ipAddress }) + } + + const response = NextResponse.json({ ok: true }) + response.cookies.set('mc-session', '', { + ...getMcSessionCookieOptions({ maxAgeSeconds: 0 }), + }) + + return response +} diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..8a4f0d8fad92ce6ee3f7475ec0f696a03fe7024d --- /dev/null +++ b/src/app/api/auth/me/route.ts @@ -0,0 +1,108 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getUserFromRequest, updateUser } from '@/lib/auth' +import { logAuditEvent } from '@/lib/db' +import { verifyPassword } from '@/lib/password' + +export async function GET(request: Request) { + const user = getUserFromRequest(request) + + if (!user) { + return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }) + } + + return NextResponse.json({ + user: { + id: user.id, + username: user.username, + display_name: user.display_name, + role: user.role, + provider: user.provider || 'local', + email: user.email || null, + avatar_url: user.avatar_url || null, + }, + }) +} + +/** + * PATCH /api/auth/me - Self-service password change and display name update. + * Body: { current_password, new_password } and/or { display_name } + */ +export async function PATCH(request: NextRequest) { + const user = getUserFromRequest(request) + if (!user) { + return NextResponse.json({ error: 'Not authenticated' }, { status: 401 }) + } + + // API key users (id=0) cannot change passwords + if (user.id === 0) { + return NextResponse.json({ error: 'API key users cannot change passwords' }, { status: 403 }) + } + + try { + const { current_password, new_password, display_name } = await request.json() + + const updates: { password?: string; display_name?: string } = {} + + // Handle password change + if (new_password) { + if (!current_password) { + return NextResponse.json({ error: 'Current password is required' }, { status: 400 }) + } + + if (new_password.length < 8) { + return NextResponse.json({ error: 'New password must be at least 8 characters' }, { status: 400 }) + } + + // Verify current password by fetching stored hash + const { getDatabase } = await import('@/lib/db') + const db = getDatabase() + const row = db.prepare('SELECT password_hash FROM users WHERE id = ?').get(user.id) as any + if (!row || !verifyPassword(current_password, row.password_hash)) { + return NextResponse.json({ error: 'Current password is incorrect' }, { status: 403 }) + } + + updates.password = new_password + } + + // Handle display name update + if (display_name !== undefined) { + if (!display_name.trim()) { + return NextResponse.json({ error: 'Display name cannot be empty' }, { status: 400 }) + } + updates.display_name = display_name.trim() + } + + if (Object.keys(updates).length === 0) { + return NextResponse.json({ error: 'No updates provided' }, { status: 400 }) + } + + const updated = updateUser(user.id, updates) + if (!updated) { + return NextResponse.json({ error: 'User not found' }, { status: 404 }) + } + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + if (updates.password) { + logAuditEvent({ action: 'password_change', actor: user.username, actor_id: user.id, ip_address: ipAddress }) + } + if (updates.display_name) { + logAuditEvent({ action: 'profile_update', actor: user.username, actor_id: user.id, detail: { display_name: updates.display_name }, ip_address: ipAddress }) + } + + return NextResponse.json({ + success: true, + user: { + id: updated.id, + username: updated.username, + display_name: updated.display_name, + role: updated.role, + provider: updated.provider || 'local', + email: updated.email || null, + avatar_url: updated.avatar_url || null, + }, + }) + } catch (error) { + console.error('PATCH /api/auth/me error:', error) + return NextResponse.json({ error: 'Failed to update profile' }, { status: 500 }) + } +} diff --git a/src/app/api/auth/users/route.ts b/src/app/api/auth/users/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..18bed8551f2eb3de98aa67dae33914b38c1923e9 --- /dev/null +++ b/src/app/api/auth/users/route.ts @@ -0,0 +1,159 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getUserFromRequest, getAllUsers, createUser, updateUser, deleteUser } from '@/lib/auth' +import { logAuditEvent } from '@/lib/db' + +/** + * GET /api/auth/users - List all users (admin only) + */ +export async function GET(request: NextRequest) { + const user = getUserFromRequest(request) + if (!user || user.role !== 'admin') { + return NextResponse.json({ error: 'Admin access required' }, { status: 403 }) + } + + const users = getAllUsers() + return NextResponse.json({ users }) +} + +/** + * POST /api/auth/users - Create a new user (admin only) + */ +export async function POST(request: NextRequest) { + const currentUser = getUserFromRequest(request) + if (!currentUser || currentUser.role !== 'admin') { + return NextResponse.json({ error: 'Admin access required' }, { status: 403 }) + } + + try { + const { username, password, display_name, role = 'operator', provider = 'local', email = null } = await request.json() + + if (!username || !password) { + return NextResponse.json({ error: 'Username and password are required' }, { status: 400 }) + } + + if (!['admin', 'operator', 'viewer'].includes(role)) { + return NextResponse.json({ error: 'Invalid role' }, { status: 400 }) + } + + const newUser = createUser(username, password, display_name || username, role, { provider, email }) + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'user_create', actor: currentUser.username, actor_id: currentUser.id, + target_type: 'user', target_id: newUser.id, + detail: { username, role, provider, email }, ip_address: ipAddress, + }) + + return NextResponse.json({ + user: { + id: newUser.id, + username: newUser.username, + display_name: newUser.display_name, + role: newUser.role, + provider: newUser.provider || 'local', + email: newUser.email || null, + avatar_url: newUser.avatar_url || null, + is_approved: newUser.is_approved ?? 1, + } + }, { status: 201 }) + } catch (error: any) { + if (error.message?.includes('UNIQUE constraint failed')) { + return NextResponse.json({ error: 'Username already exists' }, { status: 409 }) + } + console.error('POST /api/auth/users error:', error) + return NextResponse.json({ error: 'Failed to create user' }, { status: 500 }) + } +} + +/** + * PUT /api/auth/users - Update a user (admin only) + */ +export async function PUT(request: NextRequest) { + const currentUser = getUserFromRequest(request) + if (!currentUser || currentUser.role !== 'admin') { + return NextResponse.json({ error: 'Admin access required' }, { status: 403 }) + } + + try { + const { id, display_name, role, password, is_approved, email, avatar_url } = await request.json() + + if (!id) { + return NextResponse.json({ error: 'User ID is required' }, { status: 400 }) + } + + if (role && !['admin', 'operator', 'viewer'].includes(role)) { + return NextResponse.json({ error: 'Invalid role' }, { status: 400 }) + } + + // Prevent demoting yourself + if (id === currentUser.id && role && role !== currentUser.role) { + return NextResponse.json({ error: 'Cannot change your own role' }, { status: 400 }) + } + + const updated = updateUser(id, { display_name, role, password: password || undefined, is_approved, email, avatar_url }) + if (!updated) { + return NextResponse.json({ error: 'User not found' }, { status: 404 }) + } + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'user_update', actor: currentUser.username, actor_id: currentUser.id, + target_type: 'user', target_id: id, + detail: { display_name, role, password_changed: !!password, is_approved }, ip_address: ipAddress, + }) + + return NextResponse.json({ + user: { + id: updated.id, + username: updated.username, + display_name: updated.display_name, + role: updated.role, + provider: updated.provider || 'local', + email: updated.email || null, + avatar_url: updated.avatar_url || null, + is_approved: updated.is_approved ?? 1, + } + }) + } catch (error) { + console.error('PUT /api/auth/users error:', error) + return NextResponse.json({ error: 'Failed to update user' }, { status: 500 }) + } +} + +/** + * DELETE /api/auth/users - Delete a user (admin only) + */ +export async function DELETE(request: NextRequest) { + const currentUser = getUserFromRequest(request) + if (!currentUser || currentUser.role !== 'admin') { + return NextResponse.json({ error: 'Admin access required' }, { status: 403 }) + } + + const { searchParams } = new URL(request.url) + const id = searchParams.get('id') + + if (!id) { + return NextResponse.json({ error: 'User ID is required' }, { status: 400 }) + } + + const userId = parseInt(id) + + // Prevent deleting yourself + if (userId === currentUser.id) { + return NextResponse.json({ error: 'Cannot delete your own account' }, { status: 400 }) + } + + const deleted = deleteUser(userId) + if (!deleted) { + return NextResponse.json({ error: 'User not found' }, { status: 404 }) + } + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'user_delete', actor: currentUser.username, actor_id: currentUser.id, + target_type: 'user', target_id: userId, + ip_address: ipAddress, + }) + + return NextResponse.json({ success: true }) +} diff --git a/src/app/api/backup/route.ts b/src/app/api/backup/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..18d120b11e41a9fc9b7607eedea054380cb0ab10 --- /dev/null +++ b/src/app/api/backup/route.ts @@ -0,0 +1,129 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase, logAuditEvent } from '@/lib/db' +import { config, ensureDirExists } from '@/lib/config' +import { join, dirname } from 'path' +import { readdirSync, statSync, unlinkSync } from 'fs' + +const BACKUP_DIR = join(dirname(config.dbPath), 'backups') +const MAX_BACKUPS = 10 + +/** + * GET /api/backup - List existing backups (admin only) + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + ensureDirExists(BACKUP_DIR) + + try { + const files = readdirSync(BACKUP_DIR) + .filter(f => f.endsWith('.db')) + .map(f => { + const stat = statSync(join(BACKUP_DIR, f)) + return { + name: f, + size: stat.size, + created_at: Math.floor(stat.mtimeMs / 1000), + } + }) + .sort((a, b) => b.created_at - a.created_at) + + return NextResponse.json({ backups: files, dir: BACKUP_DIR }) + } catch { + return NextResponse.json({ backups: [], dir: BACKUP_DIR }) + } +} + +/** + * POST /api/backup - Create a new backup (admin only) + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + ensureDirExists(BACKUP_DIR) + + const timestamp = new Date().toISOString().replace(/[:.]/g, '-').replace('T', '_').slice(0, 19) + const backupPath = join(BACKUP_DIR, `mc-backup-${timestamp}.db`) + + try { + const db = getDatabase() + await db.backup(backupPath) + + const stat = statSync(backupPath) + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'backup_create', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { path: backupPath, size: stat.size }, + ip_address: ipAddress, + }) + + // Prune old backups beyond MAX_BACKUPS + pruneOldBackups() + + return NextResponse.json({ + success: true, + backup: { + name: `mc-backup-${timestamp}.db`, + size: stat.size, + created_at: Math.floor(stat.mtimeMs / 1000), + }, + }) + } catch (error: any) { + console.error('Backup failed:', error) + return NextResponse.json({ error: `Backup failed: ${error.message}` }, { status: 500 }) + } +} + +/** + * DELETE /api/backup?name= - Delete a specific backup (admin only) + */ +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const { searchParams } = new URL(request.url) + const name = searchParams.get('name') + + if (!name || !name.endsWith('.db') || name.includes('/') || name.includes('..')) { + return NextResponse.json({ error: 'Invalid backup name' }, { status: 400 }) + } + + try { + const fullPath = join(BACKUP_DIR, name) + unlinkSync(fullPath) + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'backup_delete', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { name }, + ip_address: ipAddress, + }) + + return NextResponse.json({ success: true }) + } catch { + return NextResponse.json({ error: 'Backup not found' }, { status: 404 }) + } +} + +function pruneOldBackups() { + try { + const files = readdirSync(BACKUP_DIR) + .filter(f => f.startsWith('mc-backup-') && f.endsWith('.db')) + .map(f => ({ name: f, mtime: statSync(join(BACKUP_DIR, f)).mtimeMs })) + .sort((a, b) => b.mtime - a.mtime) + + for (const file of files.slice(MAX_BACKUPS)) { + unlinkSync(join(BACKUP_DIR, file.name)) + } + } catch { + // Best-effort pruning + } +} diff --git a/src/app/api/chat/conversations/route.ts b/src/app/api/chat/conversations/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..1b32f091fac6ebd5afe66aa27817bda5b76cc98e --- /dev/null +++ b/src/app/api/chat/conversations/route.ts @@ -0,0 +1,79 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase } from '@/lib/db' + +/** + * GET /api/chat/conversations - List conversations derived from messages + * Query params: agent (filter by participant), limit, offset + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + + const agent = searchParams.get('agent') + const limit = parseInt(searchParams.get('limit') || '50') + const offset = parseInt(searchParams.get('offset') || '0') + + let query: string + const params: any[] = [] + + if (agent) { + // Get conversations where this agent is a participant + query = ` + SELECT + m.conversation_id, + MAX(m.created_at) as last_message_at, + COUNT(*) as message_count, + COUNT(DISTINCT m.from_agent) + COUNT(DISTINCT CASE WHEN m.to_agent IS NOT NULL THEN m.to_agent END) as participant_count, + SUM(CASE WHEN m.to_agent = ? AND m.read_at IS NULL THEN 1 ELSE 0 END) as unread_count + FROM messages m + WHERE m.from_agent = ? OR m.to_agent = ? OR m.to_agent IS NULL + GROUP BY m.conversation_id + ORDER BY last_message_at DESC + LIMIT ? OFFSET ? + ` + params.push(agent, agent, agent, limit, offset) + } else { + query = ` + SELECT + m.conversation_id, + MAX(m.created_at) as last_message_at, + COUNT(*) as message_count, + COUNT(DISTINCT m.from_agent) + COUNT(DISTINCT CASE WHEN m.to_agent IS NOT NULL THEN m.to_agent END) as participant_count, + 0 as unread_count + FROM messages m + GROUP BY m.conversation_id + ORDER BY last_message_at DESC + LIMIT ? OFFSET ? + ` + params.push(limit, offset) + } + + const conversations = db.prepare(query).all(...params) as any[] + + // Fetch the last message for each conversation + const withLastMessage = conversations.map((conv) => { + const lastMsg = db.prepare(` + SELECT * FROM messages + WHERE conversation_id = ? + ORDER BY created_at DESC + LIMIT 1 + `).get(conv.conversation_id) as any + + return { + ...conv, + last_message: lastMsg + ? { + ...lastMsg, + metadata: lastMsg.metadata ? JSON.parse(lastMsg.metadata) : null + } + : null + } + }) + + return NextResponse.json({ conversations: withLastMessage, total: withLastMessage.length }) + } catch (error) { + console.error('GET /api/chat/conversations error:', error) + return NextResponse.json({ error: 'Failed to fetch conversations' }, { status: 500 }) + } +} diff --git a/src/app/api/chat/messages/[id]/route.ts b/src/app/api/chat/messages/[id]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..0995d44aba4f993028c3886cbb0dcb38e30282d2 --- /dev/null +++ b/src/app/api/chat/messages/[id]/route.ts @@ -0,0 +1,72 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, Message } from '@/lib/db' +import { requireRole } from '@/lib/auth' + +/** + * GET /api/chat/messages/[id] - Get a single message + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const db = getDatabase() + const { id } = await params + + const message = db.prepare('SELECT * FROM messages WHERE id = ?').get(parseInt(id)) as Message | undefined + + if (!message) { + return NextResponse.json({ error: 'Message not found' }, { status: 404 }) + } + + return NextResponse.json({ + message: { + ...message, + metadata: message.metadata ? JSON.parse(message.metadata) : null + } + }) + } catch (error) { + console.error('GET /api/chat/messages/[id] error:', error) + return NextResponse.json({ error: 'Failed to fetch message' }, { status: 500 }) + } +} + +/** + * PATCH /api/chat/messages/[id] - Mark message as read + */ +export async function PATCH( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const { id } = await params + const body = await request.json() + + const message = db.prepare('SELECT * FROM messages WHERE id = ?').get(parseInt(id)) as Message | undefined + + if (!message) { + return NextResponse.json({ error: 'Message not found' }, { status: 404 }) + } + + if (body.read) { + const now = Math.floor(Date.now() / 1000) + db.prepare('UPDATE messages SET read_at = ? WHERE id = ?').run(now, parseInt(id)) + } + + const updated = db.prepare('SELECT * FROM messages WHERE id = ?').get(parseInt(id)) as Message + + return NextResponse.json({ + message: { + ...updated, + metadata: updated.metadata ? JSON.parse(updated.metadata) : null + } + }) + } catch (error) { + console.error('PATCH /api/chat/messages/[id] error:', error) + return NextResponse.json({ error: 'Failed to update message' }, { status: 500 }) + } +} diff --git a/src/app/api/chat/messages/route.ts b/src/app/api/chat/messages/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..78991cb949b901350cb32bc6c64844df6e8b60ad --- /dev/null +++ b/src/app/api/chat/messages/route.ts @@ -0,0 +1,459 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers, Message } from '@/lib/db' +import { runOpenClaw } from '@/lib/command' +import { getAllGatewaySessions } from '@/lib/sessions' +import { eventBus } from '@/lib/event-bus' +import { requireRole } from '@/lib/auth' + +type ForwardInfo = { + attempted: boolean + delivered: boolean + reason?: string + session?: string + runId?: string +} + +const COORDINATOR_AGENT = + String(process.env.MC_COORDINATOR_AGENT || process.env.NEXT_PUBLIC_COORDINATOR_AGENT || 'coordinator').trim() || + 'coordinator' + +function parseGatewayJson(raw: string): any | null { + const trimmed = String(raw || '').trim() + if (!trimmed) return null + const start = trimmed.indexOf('{') + const end = trimmed.lastIndexOf('}') + if (start < 0 || end < start) return null + try { + return JSON.parse(trimmed.slice(start, end + 1)) + } catch { + return null + } +} + +function createChatReply( + db: ReturnType, + conversationId: string, + fromAgent: string, + toAgent: string, + content: string, + messageType: 'text' | 'status' = 'status', + metadata: Record | null = null +) { + const replyInsert = db + .prepare(` + INSERT INTO messages (conversation_id, from_agent, to_agent, content, message_type, metadata) + VALUES (?, ?, ?, ?, ?, ?) + `) + .run( + conversationId, + fromAgent, + toAgent, + content, + messageType, + metadata ? JSON.stringify(metadata) : null + ) + + const row = db + .prepare('SELECT * FROM messages WHERE id = ?') + .get(replyInsert.lastInsertRowid) as Message + + eventBus.broadcast('chat.message', { + ...row, + metadata: row.metadata ? JSON.parse(row.metadata) : null, + }) +} + +function extractReplyText(waitPayload: any): string | null { + if (!waitPayload || typeof waitPayload !== 'object') return null + + const directCandidates = [ + waitPayload.text, + waitPayload.message, + waitPayload.response, + waitPayload.output, + waitPayload.result, + ] + for (const value of directCandidates) { + if (typeof value === 'string' && value.trim()) return value.trim() + } + + if (typeof waitPayload.output === 'object' && waitPayload.output) { + const nested = [ + waitPayload.output.text, + waitPayload.output.message, + waitPayload.output.content, + ] + for (const value of nested) { + if (typeof value === 'string' && value.trim()) return value.trim() + } + } + + return null +} + +/** + * GET /api/chat/messages - List messages with filters + * Query params: conversation_id, from_agent, to_agent, limit, offset, since + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + + const conversation_id = searchParams.get('conversation_id') + const from_agent = searchParams.get('from_agent') + const to_agent = searchParams.get('to_agent') + const limit = parseInt(searchParams.get('limit') || '50') + const offset = parseInt(searchParams.get('offset') || '0') + const since = searchParams.get('since') + + let query = 'SELECT * FROM messages WHERE 1=1' + const params: any[] = [] + + if (conversation_id) { + query += ' AND conversation_id = ?' + params.push(conversation_id) + } + + if (from_agent) { + query += ' AND from_agent = ?' + params.push(from_agent) + } + + if (to_agent) { + query += ' AND to_agent = ?' + params.push(to_agent) + } + + if (since) { + query += ' AND created_at > ?' + params.push(parseInt(since)) + } + + query += ' ORDER BY created_at ASC LIMIT ? OFFSET ?' + params.push(limit, offset) + + const messages = db.prepare(query).all(...params) as Message[] + + const parsed = messages.map((msg) => ({ + ...msg, + metadata: msg.metadata ? JSON.parse(msg.metadata) : null + })) + + return NextResponse.json({ messages: parsed, total: parsed.length }) + } catch (error) { + console.error('GET /api/chat/messages error:', error) + return NextResponse.json({ error: 'Failed to fetch messages' }, { status: 500 }) + } +} + +/** + * POST /api/chat/messages - Send a new message + * Body: { from, to, content, message_type, conversation_id, metadata } + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + + const from = (body.from || '').trim() + const to = body.to ? (body.to as string).trim() : null + const content = (body.content || '').trim() + const message_type = body.message_type || 'text' + const conversation_id = body.conversation_id || `conv_${Date.now()}` + const metadata = body.metadata || null + + if (!from || !content) { + return NextResponse.json( + { error: '"from" and "content" are required' }, + { status: 400 } + ) + } + + const stmt = db.prepare(` + INSERT INTO messages (conversation_id, from_agent, to_agent, content, message_type, metadata) + VALUES (?, ?, ?, ?, ?, ?) + `) + + const result = stmt.run( + conversation_id, + from, + to, + content, + message_type, + metadata ? JSON.stringify(metadata) : null + ) + + const messageId = result.lastInsertRowid as number + + let forwardInfo: ForwardInfo | null = null + + // Log activity + db_helpers.logActivity( + 'chat_message', + 'message', + messageId, + from, + `Sent ${message_type} message${to ? ` to ${to}` : ' (broadcast)'}`, + { conversation_id, to, message_type } + ) + + // Create notification for recipient if specified + if (to) { + db_helpers.createNotification( + to, + 'chat_message', + `Message from ${from}`, + content.substring(0, 200) + (content.length > 200 ? '...' : ''), + 'message', + messageId + ) + + // Optionally forward to agent via gateway + if (body.forward) { + forwardInfo = { attempted: true, delivered: false } + + const agent = db + .prepare('SELECT * FROM agents WHERE lower(name) = lower(?)') + .get(to) as any + + let sessionKey: string | null = agent?.session_key || null + + // Fallback: derive session from on-disk gateway session stores + if (!sessionKey) { + const sessions = getAllGatewaySessions() + const match = sessions.find( + (s) => s.agent.toLowerCase() === String(to).toLowerCase() + ) + sessionKey = match?.key || match?.sessionId || null + } + + // Prefer configured openclawId when present, fallback to normalized name + let openclawAgentId: string | null = null + if (agent?.config) { + try { + const cfg = JSON.parse(agent.config) + if (cfg?.openclawId && typeof cfg.openclawId === 'string') { + openclawAgentId = cfg.openclawId + } + } catch { + // ignore parse issues + } + } + if (!openclawAgentId && typeof to === 'string') { + openclawAgentId = to.toLowerCase().replace(/\s+/g, '-') + } + + if (!sessionKey && !openclawAgentId) { + forwardInfo.reason = 'no_active_session' + + // For coordinator messages, emit an immediate visible status reply + if (typeof conversation_id === 'string' && conversation_id.startsWith('coord:')) { + try { + createChatReply( + db, + conversation_id, + COORDINATOR_AGENT, + from, + 'I received your message, but my live coordinator session is offline right now. Start/restore the coordinator session and retry.', + 'status', + { status: 'offline', reason: 'no_active_session' } + ) + } catch (e) { + console.error('Failed to create offline status reply:', e) + } + } + } else { + try { + const invokeParams: any = { + message: `Message from ${from}: ${content}`, + idempotencyKey: `mc-${messageId}-${Date.now()}`, + deliver: false, + } + if (sessionKey) invokeParams.sessionKey = sessionKey + else invokeParams.agentId = openclawAgentId + + const invokeResult = await runOpenClaw( + [ + 'gateway', + 'call', + 'agent', + '--timeout', + '10000', + '--params', + JSON.stringify(invokeParams), + '--json', + ], + { timeoutMs: 12000 } + ) + const acceptedPayload = parseGatewayJson(invokeResult.stdout) + forwardInfo.delivered = true + forwardInfo.session = sessionKey || openclawAgentId || undefined + if (typeof acceptedPayload?.runId === 'string' && acceptedPayload.runId) { + forwardInfo.runId = acceptedPayload.runId + } + } catch (err) { + // OpenClaw may return accepted JSON on stdout but still emit a late stderr warning. + // Treat accepted runs as successful delivery. + const maybeStdout = String((err as any)?.stdout || '') + const acceptedPayload = parseGatewayJson(maybeStdout) + if (maybeStdout.includes('"status": "accepted"') || maybeStdout.includes('"status":"accepted"')) { + forwardInfo.delivered = true + forwardInfo.session = sessionKey || openclawAgentId || undefined + if (typeof acceptedPayload?.runId === 'string' && acceptedPayload.runId) { + forwardInfo.runId = acceptedPayload.runId + } + } else { + forwardInfo.reason = 'gateway_send_failed' + console.error('Failed to forward message via gateway:', err) + + // For coordinator messages, emit visible status when send fails + if (typeof conversation_id === 'string' && conversation_id.startsWith('coord:')) { + try { + createChatReply( + db, + conversation_id, + COORDINATOR_AGENT, + from, + 'I received your message, but delivery to the live coordinator runtime failed. Please restart the coordinator/gateway session and retry.', + 'status', + { status: 'delivery_failed', reason: 'gateway_send_failed' } + ) + } catch (e) { + console.error('Failed to create gateway failure status reply:', e) + } + } + } + } + + // Coordinator mode should always show visible coordinator feedback in thread. + if ( + typeof conversation_id === 'string' && + conversation_id.startsWith('coord:') && + forwardInfo.delivered + ) { + try { + createChatReply( + db, + conversation_id, + COORDINATOR_AGENT, + from, + 'Received. I am coordinating downstream agents now.', + 'status', + { status: 'accepted', runId: forwardInfo.runId || null } + ) + } catch (e) { + console.error('Failed to create accepted status reply:', e) + } + + // Best effort: wait briefly and surface completion/error feedback. + if (forwardInfo.runId) { + try { + const waitResult = await runOpenClaw( + [ + 'gateway', + 'call', + 'agent.wait', + '--timeout', + '8000', + '--params', + JSON.stringify({ runId: forwardInfo.runId, timeoutMs: 6000 }), + '--json', + ], + { timeoutMs: 9000 } + ) + + const waitPayload = parseGatewayJson(waitResult.stdout) + const waitStatus = String(waitPayload?.status || '').toLowerCase() + + if (waitStatus === 'error') { + const reason = + typeof waitPayload?.error === 'string' + ? waitPayload.error + : 'Unknown runtime error' + createChatReply( + db, + conversation_id, + COORDINATOR_AGENT, + from, + `I received your message, but execution failed: ${reason}`, + 'status', + { status: 'error', runId: forwardInfo.runId } + ) + } else if (waitStatus === 'timeout') { + createChatReply( + db, + conversation_id, + COORDINATOR_AGENT, + from, + 'I received your message and I am still processing it. I will post results as soon as execution completes.', + 'status', + { status: 'processing', runId: forwardInfo.runId } + ) + } else { + const replyText = extractReplyText(waitPayload) + if (replyText) { + createChatReply( + db, + conversation_id, + COORDINATOR_AGENT, + from, + replyText, + 'text', + { status: waitStatus || 'completed', runId: forwardInfo.runId } + ) + } else { + createChatReply( + db, + conversation_id, + COORDINATOR_AGENT, + from, + 'Execution accepted and completed. No textual response payload was returned by the runtime.', + 'status', + { status: waitStatus || 'completed', runId: forwardInfo.runId } + ) + } + } + } catch (waitErr) { + const maybeWaitStdout = String((waitErr as any)?.stdout || '') + const maybeWaitStderr = String((waitErr as any)?.stderr || '') + const waitPayload = parseGatewayJson(maybeWaitStdout) + const reason = + typeof waitPayload?.error === 'string' + ? waitPayload.error + : (maybeWaitStderr || maybeWaitStdout || 'Unable to read completion status from coordinator runtime.').trim() + + createChatReply( + db, + conversation_id, + COORDINATOR_AGENT, + from, + `I received your message, but I could not retrieve completion output yet: ${reason}`, + 'status', + { status: 'unknown', runId: forwardInfo.runId } + ) + } + } + } + } + } + } + + const created = db.prepare('SELECT * FROM messages WHERE id = ?').get(messageId) as Message + const parsedMessage = { + ...created, + metadata: created.metadata ? JSON.parse(created.metadata) : null + } + + // Broadcast to SSE clients + eventBus.broadcast('chat.message', parsedMessage) + + return NextResponse.json({ message: parsedMessage, forward: forwardInfo }, { status: 201 }) + } catch (error) { + console.error('POST /api/chat/messages error:', error) + return NextResponse.json({ error: 'Failed to send message' }, { status: 500 }) + } +} diff --git a/src/app/api/cleanup/route.ts b/src/app/api/cleanup/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..90d38637b0521e2be1b0ef0f21d73e038d0a83bd --- /dev/null +++ b/src/app/api/cleanup/route.ts @@ -0,0 +1,162 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase, logAuditEvent } from '@/lib/db' +import { config } from '@/lib/config' + +interface CleanupResult { + table: string + deleted: number + cutoff_date: string + retention_days: number +} + +/** + * GET /api/cleanup - Show retention policy and what would be cleaned + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + const now = Math.floor(Date.now() / 1000) + const ret = config.retention + + const preview = [] + + for (const { table, column, days, label } of getRetentionTargets()) { + if (days <= 0) { + preview.push({ table: label, retention_days: 0, stale_count: 0, note: 'Retention disabled (keep forever)' }) + continue + } + const cutoff = now - days * 86400 + try { + const row = db.prepare(`SELECT COUNT(*) as c FROM ${table} WHERE ${column} < ?`).get(cutoff) as any + preview.push({ + table: label, + retention_days: days, + cutoff_date: new Date(cutoff * 1000).toISOString().split('T')[0], + stale_count: row.c, + }) + } catch { + preview.push({ table: label, retention_days: days, stale_count: 0, note: 'Table not found' }) + } + } + + // Token usage file stats + try { + const { readFile } = require('fs/promises') + const data = JSON.parse(await readFile(config.tokensPath, 'utf-8')) + const cutoffMs = Date.now() - ret.tokenUsage * 86400000 + const stale = data.filter((r: any) => r.timestamp < cutoffMs).length + preview.push({ + table: 'Token Usage (file)', + retention_days: ret.tokenUsage, + cutoff_date: new Date(cutoffMs).toISOString().split('T')[0], + stale_count: stale, + }) + } catch { + preview.push({ table: 'Token Usage (file)', retention_days: ret.tokenUsage, stale_count: 0, note: 'No token data file' }) + } + + return NextResponse.json({ retention: config.retention, preview }) +} + +/** + * POST /api/cleanup - Run cleanup (admin only) + * Body: { dry_run?: boolean } + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const body = await request.json().catch(() => ({})) + const dryRun = body.dry_run === true + + const db = getDatabase() + const now = Math.floor(Date.now() / 1000) + const results: CleanupResult[] = [] + let totalDeleted = 0 + + for (const { table, column, days, label } of getRetentionTargets()) { + if (days <= 0) continue + const cutoff = now - days * 86400 + + try { + if (dryRun) { + const row = db.prepare(`SELECT COUNT(*) as c FROM ${table} WHERE ${column} < ?`).get(cutoff) as any + results.push({ + table: label, + deleted: row.c, + cutoff_date: new Date(cutoff * 1000).toISOString().split('T')[0], + retention_days: days, + }) + totalDeleted += row.c + } else { + const res = db.prepare(`DELETE FROM ${table} WHERE ${column} < ?`).run(cutoff) + results.push({ + table: label, + deleted: res.changes, + cutoff_date: new Date(cutoff * 1000).toISOString().split('T')[0], + retention_days: days, + }) + totalDeleted += res.changes + } + } catch { + results.push({ table: label, deleted: 0, cutoff_date: '', retention_days: days }) + } + } + + // Clean token usage file + const ret = config.retention + if (ret.tokenUsage > 0) { + try { + const { readFile, writeFile } = require('fs/promises') + const raw = await readFile(config.tokensPath, 'utf-8') + const data = JSON.parse(raw) + const cutoffMs = Date.now() - ret.tokenUsage * 86400000 + const kept = data.filter((r: any) => r.timestamp >= cutoffMs) + const removed = data.length - kept.length + + if (!dryRun && removed > 0) { + await writeFile(config.tokensPath, JSON.stringify(kept, null, 2)) + } + + results.push({ + table: 'Token Usage (file)', + deleted: removed, + cutoff_date: new Date(cutoffMs).toISOString().split('T')[0], + retention_days: ret.tokenUsage, + }) + totalDeleted += removed + } catch { + // No token file or parse error + } + } + + if (!dryRun && totalDeleted > 0) { + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'data_cleanup', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { total_deleted: totalDeleted, results }, + ip_address: ipAddress, + }) + } + + return NextResponse.json({ + dry_run: dryRun, + total_deleted: totalDeleted, + results, + }) +} + +function getRetentionTargets() { + const ret = config.retention + return [ + { table: 'activities', column: 'created_at', days: ret.activities, label: 'Activities' }, + { table: 'audit_log', column: 'created_at', days: ret.auditLog, label: 'Audit Log' }, + { table: 'notifications', column: 'created_at', days: ret.notifications, label: 'Notifications' }, + { table: 'pipeline_runs', column: 'created_at', days: ret.pipelineRuns, label: 'Pipeline Runs' }, + ] +} diff --git a/src/app/api/cron/route.ts b/src/app/api/cron/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..d316d148f5f5c9247d166482cd339383eeabf47a --- /dev/null +++ b/src/app/api/cron/route.ts @@ -0,0 +1,341 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { config } from '@/lib/config' +import fs from 'node:fs' +import path from 'node:path' + +interface CronJob { + name: string + schedule: string + command: string + enabled: boolean + lastRun?: number + nextRun?: number + lastStatus?: 'success' | 'error' | 'running' + lastError?: string + // Extended fields from OpenClaw format + id?: string + agentId?: string + timezone?: string + model?: string + delivery?: string +} + +/** + * OpenClaw cron jobs live in ~/.openclaw/cron/jobs.json + * Format: { version: 1, jobs: [ { id, agentId, name, enabled, schedule: { kind, expr, tz }, payload, delivery, state } ] } + */ +interface OpenClawCronJob { + id: string + agentId: string + name: string + enabled: boolean + createdAtMs?: number + updatedAtMs?: number + schedule: { + kind: string + expr: string + tz?: string + } + sessionTarget?: string + wakeMode?: string + payload: { + kind: string + message?: string + model?: string + thinking?: string + timeoutSeconds?: number + } + delivery?: { + mode: string + channel?: string + to?: string + } + state?: { + nextRunAtMs?: number + lastRunAtMs?: number + lastStatus?: string + lastDurationMs?: number + lastError?: string + } +} + +interface OpenClawCronFile { + version: number + jobs: OpenClawCronJob[] +} + +function getCronFilePath(): string { + const openclawHome = config.openclawHome + if (!openclawHome) return '' + return path.join(openclawHome, 'cron', 'jobs.json') +} + +function loadCronFile(): OpenClawCronFile | null { + const filePath = getCronFilePath() + if (!filePath) return null + try { + const raw = fs.readFileSync(filePath, 'utf-8') + return JSON.parse(raw) + } catch { + return null + } +} + +function saveCronFile(data: OpenClawCronFile): boolean { + const filePath = getCronFilePath() + if (!filePath) return false + try { + fs.writeFileSync(filePath, JSON.stringify(data, null, 2)) + return true + } catch (err) { + console.error('Failed to write cron file:', err) + return false + } +} + +function mapLastStatus(status?: string): 'success' | 'error' | 'running' | undefined { + if (!status) return undefined + const s = status.toLowerCase() + if (s === 'success' || s === 'completed' || s === 'updated') return 'success' + if (s === 'error' || s === 'failed') return 'error' + if (s === 'running' || s === 'pending') return 'running' + return 'success' // default for unknown non-error statuses +} + +function mapOpenClawJob(job: OpenClawCronJob): CronJob { + // Build a human-readable command description from the payload + const payloadSummary = job.payload.message + ? job.payload.message.slice(0, 200) + (job.payload.message.length > 200 ? '...' : '') + : `${job.payload.kind} (${job.agentId})` + + const scheduleStr = job.schedule.tz + ? `${job.schedule.expr} (${job.schedule.tz})` + : job.schedule.expr + + return { + id: job.id, + name: job.name, + schedule: scheduleStr, + command: payloadSummary, + enabled: job.enabled, + lastRun: job.state?.lastRunAtMs, + nextRun: job.state?.nextRunAtMs, + lastStatus: mapLastStatus(job.state?.lastStatus), + lastError: job.state?.lastError, + agentId: job.agentId, + timezone: job.schedule.tz, + model: job.payload.model, + delivery: job.delivery?.mode === 'none' ? undefined : job.delivery?.channel, + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') + + if (action === 'list') { + const cronFile = loadCronFile() + if (!cronFile || !cronFile.jobs) { + return NextResponse.json({ jobs: [] }) + } + + const jobs = cronFile.jobs.map(mapOpenClawJob) + return NextResponse.json({ jobs }) + } + + if (action === 'logs') { + const jobId = searchParams.get('job') + if (!jobId) { + return NextResponse.json({ error: 'Job ID required' }, { status: 400 }) + } + + // Find the job to get its state info + const cronFile = loadCronFile() + const job = cronFile?.jobs.find(j => j.id === jobId || j.name === jobId) + + const logs: Array<{ timestamp: number; message: string; level: string }> = [] + + if (job?.state) { + if (job.state.lastRunAtMs) { + logs.push({ + timestamp: job.state.lastRunAtMs, + message: `Job executed — status: ${job.state.lastStatus || 'unknown'}${job.state.lastDurationMs ? ` (${job.state.lastDurationMs}ms)` : ''}`, + level: job.state.lastStatus === 'error' || job.state.lastStatus === 'failed' ? 'error' : 'info', + }) + } + if (job.state.lastError) { + logs.push({ + timestamp: job.state.lastRunAtMs || Date.now(), + message: `Error: ${job.state.lastError}`, + level: 'error', + }) + } + if (job.state.nextRunAtMs) { + logs.push({ + timestamp: Date.now(), + message: `Next scheduled run: ${new Date(job.state.nextRunAtMs).toLocaleString()}`, + level: 'info', + }) + } + } + + return NextResponse.json({ logs }) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Cron API error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const body = await request.json() + const { action, jobName, jobId } = body + + if (action === 'toggle') { + const id = jobId || jobName + if (!id) { + return NextResponse.json({ error: 'Job ID or name required' }, { status: 400 }) + } + + const cronFile = loadCronFile() + if (!cronFile) { + return NextResponse.json({ error: 'Cron file not found' }, { status: 404 }) + } + + const job = cronFile.jobs.find(j => j.id === id || j.name === id) + if (!job) { + return NextResponse.json({ error: 'Job not found' }, { status: 404 }) + } + + job.enabled = !job.enabled + job.updatedAtMs = Date.now() + + if (!saveCronFile(cronFile)) { + return NextResponse.json({ error: 'Failed to save cron file' }, { status: 500 }) + } + + return NextResponse.json({ success: true, enabled: job.enabled }) + } + + if (action === 'trigger') { + const id = jobId || jobName + if (!id) { + return NextResponse.json({ error: 'Job ID required' }, { status: 400 }) + } + + if (process.env.MISSION_CONTROL_ALLOW_COMMAND_TRIGGER !== '1') { + return NextResponse.json( + { error: 'Manual triggers disabled. Set MISSION_CONTROL_ALLOW_COMMAND_TRIGGER=1 to enable.' }, + { status: 403 } + ) + } + + const cronFile = loadCronFile() + const job = cronFile?.jobs.find(j => j.id === id || j.name === id) + if (!job) { + return NextResponse.json({ error: 'Job not found' }, { status: 404 }) + } + + // For OpenClaw cron jobs, trigger via the openclaw CLI + const { runCommand } = await import('@/lib/command') + try { + const { stdout, stderr } = await runCommand(config.openclawBin, [ + 'cron', 'trigger', job.id + ], { timeoutMs: 30000 }) + + return NextResponse.json({ + success: true, + stdout: stdout.trim(), + stderr: stderr.trim() + }) + } catch (execError: any) { + return NextResponse.json({ + success: false, + error: execError.message, + stdout: execError.stdout?.trim() || '', + stderr: execError.stderr?.trim() || '' + }, { status: 500 }) + } + } + + if (action === 'remove') { + const id = jobId || jobName + if (!id) { + return NextResponse.json({ error: 'Job ID or name required' }, { status: 400 }) + } + + const cronFile = loadCronFile() + if (!cronFile) { + return NextResponse.json({ error: 'Cron file not found' }, { status: 404 }) + } + + const idx = cronFile.jobs.findIndex(j => j.id === id || j.name === id) + if (idx === -1) { + return NextResponse.json({ error: 'Job not found' }, { status: 404 }) + } + + cronFile.jobs.splice(idx, 1) + + if (!saveCronFile(cronFile)) { + return NextResponse.json({ error: 'Failed to save cron file' }, { status: 500 }) + } + + return NextResponse.json({ success: true }) + } + + if (action === 'add') { + const { schedule, command, description } = body + const name = jobName || body.name + if (!schedule || !command || !name) { + return NextResponse.json( + { error: 'Schedule, command, and name required' }, + { status: 400 } + ) + } + + const cronFile = loadCronFile() || { version: 1, jobs: [] } + + const newJob: OpenClawCronJob = { + id: `mc-${Date.now().toString(36)}`, + agentId: String(process.env.MC_CRON_AGENT_ID || process.env.MC_COORDINATOR_AGENT || 'system'), + name, + enabled: true, + createdAtMs: Date.now(), + updatedAtMs: Date.now(), + schedule: { + kind: 'cron', + expr: schedule, + }, + payload: { + kind: 'agentTurn', + message: command, + }, + delivery: { + mode: 'none', + }, + state: {}, + } + + cronFile.jobs.push(newJob) + + if (!saveCronFile(cronFile)) { + return NextResponse.json({ error: 'Failed to save cron file' }, { status: 500 }) + } + + return NextResponse.json({ success: true }) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Cron management error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/src/app/api/events/route.ts b/src/app/api/events/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b1f0ecc2153362d819fd2c9b0be42a13620b7bb --- /dev/null +++ b/src/app/api/events/route.ts @@ -0,0 +1,65 @@ +import { eventBus, ServerEvent } from '@/lib/event-bus' + +export const dynamic = 'force-dynamic' +export const runtime = 'nodejs' + +/** + * GET /api/events - Server-Sent Events stream for real-time DB mutations. + * Clients connect via EventSource and receive JSON-encoded events. + */ +export async function GET() { + const encoder = new TextEncoder() + + // Cleanup function, set in start(), called in cancel() + let cleanup: (() => void) | null = null + + const stream = new ReadableStream({ + start(controller) { + // Send initial connection event + controller.enqueue( + encoder.encode(`data: ${JSON.stringify({ type: 'connected', data: null, timestamp: Date.now() })}\n\n`) + ) + + // Forward all server events to this SSE client + const handler = (event: ServerEvent) => { + try { + controller.enqueue( + encoder.encode(`data: ${JSON.stringify(event)}\n\n`) + ) + } catch { + // Client disconnected, cleanup will happen in cancel() + } + } + + eventBus.on('server-event', handler) + + // Heartbeat every 30s to keep connection alive through proxies + const heartbeat = setInterval(() => { + try { + controller.enqueue(encoder.encode(': heartbeat\n\n')) + } catch { + clearInterval(heartbeat) + } + }, 30_000) + + cleanup = () => { + eventBus.off('server-event', handler) + clearInterval(heartbeat) + } + }, + + cancel() { + // Client disconnected + if (cleanup) cleanup() + }, + }) + + return new Response(stream, { + headers: { + 'Content-Type': 'text/event-stream', + 'Cache-Control': 'no-cache, no-transform', + Connection: 'keep-alive', + 'X-Accel-Buffering': 'no', // Disable nginx buffering + }, + }) +} diff --git a/src/app/api/export/route.ts b/src/app/api/export/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..ec7c929e694a86663eac35a49cb56543c26027d2 --- /dev/null +++ b/src/app/api/export/route.ts @@ -0,0 +1,117 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase, logAuditEvent } from '@/lib/db' + +/** + * GET /api/export?type=audit|tasks|activities|pipelines&format=csv|json&since=UNIX&until=UNIX + * Admin-only data export endpoint. + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const { searchParams } = new URL(request.url) + const type = searchParams.get('type') + const format = searchParams.get('format') || 'csv' + const since = searchParams.get('since') + const until = searchParams.get('until') + + if (!type || !['audit', 'tasks', 'activities', 'pipelines'].includes(type)) { + return NextResponse.json( + { error: 'type required: audit, tasks, activities, pipelines' }, + { status: 400 } + ) + } + + const db = getDatabase() + const conditions: string[] = [] + const params: any[] = [] + + if (since) { + conditions.push('created_at >= ?') + params.push(parseInt(since)) + } + if (until) { + conditions.push('created_at <= ?') + params.push(parseInt(until)) + } + + const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '' + + let rows: any[] = [] + let headers: string[] = [] + let filename = '' + + switch (type) { + case 'audit': { + rows = db.prepare(`SELECT * FROM audit_log ${where} ORDER BY created_at DESC`).all(...params) + headers = ['id', 'action', 'actor', 'actor_id', 'target_type', 'target_id', 'detail', 'ip_address', 'user_agent', 'created_at'] + filename = 'audit-log' + break + } + case 'tasks': { + rows = db.prepare(`SELECT * FROM tasks ${where} ORDER BY created_at DESC`).all(...params) + headers = ['id', 'title', 'description', 'status', 'priority', 'assigned_to', 'created_by', 'created_at', 'updated_at', 'due_date', 'estimated_hours', 'actual_hours', 'tags'] + filename = 'tasks' + break + } + case 'activities': { + rows = db.prepare(`SELECT * FROM activities ${where} ORDER BY created_at DESC`).all(...params) + headers = ['id', 'type', 'entity_type', 'entity_id', 'actor', 'description', 'data', 'created_at'] + filename = 'activities' + break + } + case 'pipelines': { + rows = db.prepare(`SELECT pr.*, wp.name as pipeline_name FROM pipeline_runs pr LEFT JOIN workflow_pipelines wp ON pr.pipeline_id = wp.id ${where ? where.replace('created_at', 'pr.created_at') : ''} ORDER BY pr.created_at DESC`).all(...params) + headers = ['id', 'pipeline_id', 'pipeline_name', 'status', 'current_step', 'steps_snapshot', 'started_at', 'completed_at', 'triggered_by', 'created_at'] + filename = 'pipeline-runs' + break + } + } + + // Log the export + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'data_export', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { type, format, row_count: rows.length }, + ip_address: ipAddress, + }) + + const dateStr = new Date().toISOString().split('T')[0] + + if (format === 'csv') { + const csvRows = [headers.join(',')] + for (const row of rows) { + const values = headers.map(h => { + const val = row[h] + if (val == null) return '' + const str = String(val) + // Escape CSV: wrap in quotes if contains comma, newline, or quote + if (str.includes(',') || str.includes('\n') || str.includes('"')) { + return `"${str.replace(/"/g, '""')}"` + } + return str + }) + csvRows.push(values.join(',')) + } + + return new NextResponse(csvRows.join('\n'), { + headers: { + 'Content-Type': 'text/csv; charset=utf-8', + 'Content-Disposition': `attachment; filename=${filename}-${dateStr}.csv`, + }, + }) + } + + // JSON format + return NextResponse.json( + { type, exported_at: new Date().toISOString(), count: rows.length, data: rows }, + { + headers: { + 'Content-Disposition': `attachment; filename=${filename}-${dateStr}.json`, + }, + } + ) +} diff --git a/src/app/api/gateway-config/route.ts b/src/app/api/gateway-config/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..bd7e84d9ea607d285b0fcf93510242a114777065 --- /dev/null +++ b/src/app/api/gateway-config/route.ts @@ -0,0 +1,132 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { logAuditEvent } from '@/lib/db' +import { config } from '@/lib/config' +import { join } from 'path' + +function getConfigPath(): string | null { + if (!config.openclawHome) return null + return join(config.openclawHome, 'openclaw.json') +} + +/** + * GET /api/gateway-config - Read the gateway configuration + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const configPath = getConfigPath() + if (!configPath) { + return NextResponse.json({ error: 'OPENCLAW_HOME not configured' }, { status: 404 }) + } + + try { + const { readFile } = require('fs/promises') + const raw = await readFile(configPath, 'utf-8') + const parsed = JSON.parse(raw) + + // Redact sensitive fields for display + const redacted = redactSensitive(JSON.parse(JSON.stringify(parsed))) + + return NextResponse.json({ + path: configPath, + config: redacted, + raw_size: raw.length, + }) + } catch (err: any) { + if (err.code === 'ENOENT') { + return NextResponse.json({ error: 'Config file not found', path: configPath }, { status: 404 }) + } + return NextResponse.json({ error: `Failed to read config: ${err.message}` }, { status: 500 }) + } +} + +/** + * PUT /api/gateway-config - Update specific config fields + * Body: { updates: { "path.to.key": value, ... } } + * + * Uses dot-notation paths to set nested values. + * CRITICAL: Preserves gateway.auth.password and other sensitive fields. + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const configPath = getConfigPath() + if (!configPath) { + return NextResponse.json({ error: 'OPENCLAW_HOME not configured' }, { status: 404 }) + } + + const body = await request.json().catch(() => null) + if (!body?.updates || typeof body.updates !== 'object') { + return NextResponse.json({ error: 'updates object required (dot-notation paths)' }, { status: 400 }) + } + + // Block writes to sensitive paths + const blockedPaths = ['gateway.auth.password', 'gateway.auth.secret'] + for (const key of Object.keys(body.updates)) { + if (blockedPaths.some(bp => key.startsWith(bp))) { + return NextResponse.json({ error: `Cannot modify protected field: ${key}` }, { status: 403 }) + } + } + + try { + const { readFile, writeFile } = require('fs/promises') + const raw = await readFile(configPath, 'utf-8') + const parsed = JSON.parse(raw) + + // Apply updates via dot-notation + const appliedKeys: string[] = [] + for (const [dotPath, value] of Object.entries(body.updates)) { + setNestedValue(parsed, dotPath, value) + appliedKeys.push(dotPath) + } + + // Write back with pretty formatting + await writeFile(configPath, JSON.stringify(parsed, null, 2) + '\n') + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'gateway_config_update', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { updated_keys: appliedKeys }, + ip_address: ipAddress, + }) + + return NextResponse.json({ updated: appliedKeys, count: appliedKeys.length }) + } catch (err: any) { + return NextResponse.json({ error: `Failed to update config: ${err.message}` }, { status: 500 }) + } +} + +/** Set a value in a nested object using dot-notation path */ +function setNestedValue(obj: any, path: string, value: any) { + const keys = path.split('.') + let current = obj + for (let i = 0; i < keys.length - 1; i++) { + if (current[keys[i]] === undefined) current[keys[i]] = {} + current = current[keys[i]] + } + current[keys[keys.length - 1]] = value +} + +/** Redact sensitive values for display */ +function redactSensitive(obj: any, parentKey = ''): any { + if (typeof obj !== 'object' || obj === null) return obj + + const sensitiveKeys = ['password', 'secret', 'token', 'api_key', 'apiKey'] + + for (const key of Object.keys(obj)) { + if (sensitiveKeys.some(sk => key.toLowerCase().includes(sk))) { + if (typeof obj[key] === 'string' && obj[key].length > 0) { + obj[key] = '••••••••' + } + } else if (typeof obj[key] === 'object' && obj[key] !== null) { + redactSensitive(obj[key], key) + } + } + + return obj +} diff --git a/src/app/api/gateways/health/route.ts b/src/app/api/gateways/health/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..82c78766d0545f0b2aeadd4fd0da5e983a1ebd33 --- /dev/null +++ b/src/app/api/gateways/health/route.ts @@ -0,0 +1,85 @@ +import { NextRequest, NextResponse } from "next/server" +import { requireRole } from "@/lib/auth" +import { getDatabase } from "@/lib/db" + +interface GatewayEntry { + id: number + name: string + host: string + port: number + token: string + is_primary: number + status: string +} + +interface HealthResult { + id: number + name: string + status: "online" | "offline" | "error" + latency: number | null + agents: string[] + sessions_count: number + error?: string +} + +/** + * POST /api/gateways/health - Server-side health probe for all gateways + * Probes gateways from the server where loopback addresses are reachable. + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, "viewer") + if ("error" in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + const gateways = db.prepare("SELECT * FROM gateways ORDER BY is_primary DESC, name ASC").all() as GatewayEntry[] + + const results: HealthResult[] = await Promise.all( + gateways.map(async (gw) => { + const start = Date.now() + try { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), 5000) + + const probeUrl = "http://" + gw.host + ":" + gw.port + "/" + const res = await fetch(probeUrl, { + signal: controller.signal, + }) + clearTimeout(timeout) + + const latency = Date.now() - start + const status = res.ok ? "online" : "error" + + db.prepare( + "UPDATE gateways SET status = ?, latency = ?, last_seen = (unixepoch()), updated_at = (unixepoch()) WHERE id = ?" + ).run(status, latency, gw.id) + + return { + id: gw.id, + name: gw.name, + status: status as "online" | "error", + latency, + agents: [], + sessions_count: 0, + } + } catch (err: any) { + const latency = Date.now() - start + + db.prepare( + "UPDATE gateways SET status = ?, latency = NULL, updated_at = (unixepoch()) WHERE id = ?" + ).run("offline", gw.id) + + return { + id: gw.id, + name: gw.name, + status: "offline" as const, + latency: null, + agents: [], + sessions_count: 0, + error: err.name === "AbortError" ? "timeout" : (err.message || "connection failed"), + } + } + }) + ) + + return NextResponse.json({ results, probed_at: Date.now() }) +} diff --git a/src/app/api/gateways/route.ts b/src/app/api/gateways/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..fb4d4c746d9318964ba45fc8b01d83b1e3337d3b --- /dev/null +++ b/src/app/api/gateways/route.ts @@ -0,0 +1,197 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase } from '@/lib/db' + +interface GatewayEntry { + id: number + name: string + host: string + port: number + token: string + is_primary: number + status: string + last_seen: number | null + latency: number | null + sessions_count: number + agents_count: number + created_at: number + updated_at: number +} + +function ensureTable(db: ReturnType) { + db.exec(` + CREATE TABLE IF NOT EXISTS gateways ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL UNIQUE, + host TEXT NOT NULL DEFAULT '127.0.0.1', + port INTEGER NOT NULL DEFAULT 18789, + token TEXT NOT NULL DEFAULT '', + is_primary INTEGER NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'unknown', + last_seen INTEGER, + latency INTEGER, + sessions_count INTEGER NOT NULL DEFAULT 0, + agents_count INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL DEFAULT (unixepoch()), + updated_at INTEGER NOT NULL DEFAULT (unixepoch()) + ) + `) +} + +/** + * GET /api/gateways - List all registered gateways + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'viewer') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + ensureTable(db) + + const gateways = db.prepare('SELECT * FROM gateways ORDER BY is_primary DESC, name ASC').all() as GatewayEntry[] + + // If no gateways exist, seed defaults from environment + if (gateways.length === 0) { + const name = String(process.env.MC_DEFAULT_GATEWAY_NAME || 'primary') + const host = String(process.env.OPENCLAW_GATEWAY_HOST || '127.0.0.1') + const mainPort = parseInt(process.env.OPENCLAW_GATEWAY_PORT || process.env.GATEWAY_PORT || process.env.NEXT_PUBLIC_GATEWAY_PORT || '18789') + const mainToken = + process.env.OPENCLAW_GATEWAY_TOKEN || + process.env.GATEWAY_TOKEN || + '' + + db.prepare(` + INSERT INTO gateways (name, host, port, token, is_primary) VALUES (?, ?, ?, ?, 1) + `).run(name, host, mainPort, mainToken) + + const seeded = db.prepare('SELECT * FROM gateways ORDER BY is_primary DESC, name ASC').all() as GatewayEntry[] + return NextResponse.json({ gateways: redactTokens(seeded) }) + } + + return NextResponse.json({ gateways: redactTokens(gateways) }) +} + +/** + * POST /api/gateways - Add a new gateway + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + ensureTable(db) + const body = await request.json() + + const { name, host, port, token, is_primary } = body + + if (!name || !host || !port) { + return NextResponse.json({ error: 'name, host, and port are required' }, { status: 400 }) + } + + try { + // If marking as primary, unset other primaries + if (is_primary) { + db.prepare('UPDATE gateways SET is_primary = 0').run() + } + + const result = db.prepare(` + INSERT INTO gateways (name, host, port, token, is_primary) VALUES (?, ?, ?, ?, ?) + `).run(name, host, port, token || '', is_primary ? 1 : 0) + + try { + db.prepare('INSERT INTO audit_log (action, actor, detail) VALUES (?, ?, ?)').run( + 'gateway_added', auth.user?.username || 'system', `Added gateway: ${name} (${host}:${port})` + ) + } catch { /* audit might not exist */ } + + const gw = db.prepare('SELECT * FROM gateways WHERE id = ?').get(result.lastInsertRowid) as GatewayEntry + return NextResponse.json({ gateway: redactToken(gw) }, { status: 201 }) + } catch (err: any) { + if (err.message?.includes('UNIQUE')) { + return NextResponse.json({ error: 'A gateway with that name already exists' }, { status: 409 }) + } + return NextResponse.json({ error: err.message || 'Failed to add gateway' }, { status: 500 }) + } +} + +/** + * PUT /api/gateways - Update a gateway + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + ensureTable(db) + const body = await request.json() + const { id, ...updates } = body + + if (!id) return NextResponse.json({ error: 'id is required' }, { status: 400 }) + + const existing = db.prepare('SELECT * FROM gateways WHERE id = ?').get(id) as GatewayEntry | undefined + if (!existing) return NextResponse.json({ error: 'Gateway not found' }, { status: 404 }) + + // If setting as primary, unset others + if (updates.is_primary) { + db.prepare('UPDATE gateways SET is_primary = 0').run() + } + + const allowed = ['name', 'host', 'port', 'token', 'is_primary', 'status', 'last_seen', 'latency', 'sessions_count', 'agents_count'] + const sets: string[] = [] + const values: any[] = [] + + for (const key of allowed) { + if (key in updates) { + sets.push(`${key} = ?`) + values.push(updates[key]) + } + } + + if (sets.length === 0) return NextResponse.json({ error: 'No valid fields to update' }, { status: 400 }) + + sets.push('updated_at = (unixepoch())') + values.push(id) + + db.prepare(`UPDATE gateways SET ${sets.join(', ')} WHERE id = ?`).run(...values) + + const updated = db.prepare('SELECT * FROM gateways WHERE id = ?').get(id) as GatewayEntry + return NextResponse.json({ gateway: redactToken(updated) }) +} + +/** + * DELETE /api/gateways - Remove a gateway + */ +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + ensureTable(db) + const body = await request.json() + const { id } = body + + if (!id) return NextResponse.json({ error: 'id is required' }, { status: 400 }) + + const gw = db.prepare('SELECT * FROM gateways WHERE id = ?').get(id) as GatewayEntry | undefined + if (gw?.is_primary) { + return NextResponse.json({ error: 'Cannot delete the primary gateway' }, { status: 400 }) + } + + const result = db.prepare('DELETE FROM gateways WHERE id = ?').run(id) + + try { + db.prepare('INSERT INTO audit_log (action, actor, detail) VALUES (?, ?, ?)').run( + 'gateway_removed', auth.user?.username || 'system', `Removed gateway: ${gw?.name}` + ) + } catch { /* audit might not exist */ } + + return NextResponse.json({ deleted: result.changes > 0 }) +} + +function redactToken(gw: GatewayEntry): GatewayEntry & { token_set: boolean } { + return { ...gw, token: gw.token ? '--------' : '', token_set: !!gw.token } +} + +function redactTokens(gws: GatewayEntry[]) { + return gws.map(redactToken) +} diff --git a/src/app/api/integrations/route.ts b/src/app/api/integrations/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..336018cf01fcbe239f5ef2f03997a4f2f3a5b32a --- /dev/null +++ b/src/app/api/integrations/route.ts @@ -0,0 +1,696 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { logAuditEvent } from '@/lib/db' +import { config } from '@/lib/config' +import { join } from 'path' +import { readFile, writeFile, rename } from 'fs/promises' +import { execFileSync } from 'child_process' + +// --------------------------------------------------------------------------- +// Integration registry +// --------------------------------------------------------------------------- + +interface IntegrationDef { + id: string + name: string + category: 'ai' | 'search' | 'social' | 'messaging' | 'devtools' | 'security' | 'infra' + envVars: string[] + vaultItem?: string // 1Password item name + testable?: boolean +} + +const INTEGRATIONS: IntegrationDef[] = [ + // AI Providers + { id: 'anthropic', name: 'Anthropic', category: 'ai', envVars: ['ANTHROPIC_API_KEY'], vaultItem: 'openclaw-anthropic-api-key', testable: true }, + { id: 'openai', name: 'OpenAI', category: 'ai', envVars: ['OPENAI_API_KEY'], vaultItem: 'openclaw-openai-api-key', testable: true }, + { id: 'openrouter', name: 'OpenRouter', category: 'ai', envVars: ['OPENROUTER_API_KEY'], vaultItem: 'openclaw-openrouter-api-key', testable: true }, + { id: 'nvidia', name: 'NVIDIA', category: 'ai', envVars: ['NVIDIA_API_KEY'], vaultItem: 'openclaw-nvidia-api-key' }, + { id: 'moonshot', name: 'Moonshot / Kimi', category: 'ai', envVars: ['MOONSHOT_API_KEY'], vaultItem: 'openclaw-moonshot-api-key' }, + { id: 'ollama', name: 'Ollama (Local)', category: 'ai', envVars: ['OLLAMA_API_KEY'], vaultItem: 'openclaw-ollama-api-key' }, + + // Search + { id: 'brave', name: 'Brave Search', category: 'search', envVars: ['BRAVE_API_KEY'], vaultItem: 'openclaw-brave-api-key' }, + + // Social + { id: 'x_twitter', name: 'X / Twitter', category: 'social', envVars: ['X_COOKIES_PATH'] }, + { id: 'linkedin', name: 'LinkedIn', category: 'social', envVars: ['LINKEDIN_ACCESS_TOKEN'] }, + + // Messaging + { id: 'telegram_jarv', name: 'Telegram (Jarv)', category: 'messaging', envVars: ['TELEGRAM_BOT_TOKEN_JARV'], vaultItem: 'openclaw-telegram-bot-token-jarv', testable: true }, + { id: 'telegram_forge', name: 'Telegram (Forge)', category: 'messaging', envVars: ['TELEGRAM_BOT_TOKEN_FORGE'], vaultItem: 'openclaw-telegram-bot-token-forge', testable: true }, + { id: 'telegram_nefes', name: 'Telegram (Nefes)', category: 'messaging', envVars: ['TELEGRAM_BOT_TOKEN_NEFES'], vaultItem: 'openclaw-telegram-bot-token-nefes', testable: true }, + { id: 'telegram_ops', name: 'Telegram (Ops)', category: 'messaging', envVars: ['TELEGRAM_BOT_TOKEN_OPS'], vaultItem: 'openclaw-telegram-bot-token-ops', testable: true }, + + // Dev Tools + { id: 'github', name: 'GitHub', category: 'devtools', envVars: ['GITHUB_TOKEN'], vaultItem: 'openclaw-github-token', testable: true }, + + // Security + { id: 'onepassword', name: '1Password', category: 'security', envVars: ['OP_SERVICE_ACCOUNT_TOKEN'] }, + + // Infrastructure + { id: 'gateway', name: 'Gateway Auth', category: 'infra', envVars: ['OPENCLAW_GATEWAY_TOKEN'], vaultItem: 'openclaw-openclaw-gateway-token' }, +] + +// Category metadata +const CATEGORIES: Record = { + ai: { label: 'AI Providers', order: 0 }, + search: { label: 'Search', order: 1 }, + social: { label: 'Social', order: 2 }, + messaging: { label: 'Messaging', order: 3 }, + devtools: { label: 'Dev Tools', order: 4 }, + security: { label: 'Security', order: 5 }, + infra: { label: 'Infrastructure', order: 6 }, +} + +// Vars that must never be written via this API +const BLOCKED_VARS = new Set([ + 'PATH', 'HOME', 'USER', 'SHELL', 'LANG', 'TERM', 'PWD', 'LOGNAME', 'HOSTNAME', +]) +const BLOCKED_PREFIXES = ['LD_', 'DYLD_'] + +// --------------------------------------------------------------------------- +// .env parser — preserves comments, blanks, and ordering +// --------------------------------------------------------------------------- + +interface EnvLine { + type: 'comment' | 'blank' | 'var' + raw: string + key?: string + value?: string +} + +function parseEnv(content: string): EnvLine[] { + const lines: EnvLine[] = [] + for (const raw of content.split('\n')) { + const trimmed = raw.trim() + if (trimmed === '') { + lines.push({ type: 'blank', raw }) + } else if (trimmed.startsWith('#')) { + lines.push({ type: 'comment', raw }) + } else { + const eqIdx = raw.indexOf('=') + if (eqIdx > 0) { + const key = raw.slice(0, eqIdx).trim() + const value = raw.slice(eqIdx + 1).trim() + lines.push({ type: 'var', raw, key, value }) + } else { + lines.push({ type: 'comment', raw }) // malformed line preserved as-is + } + } + } + return lines +} + +function serializeEnv(lines: EnvLine[]): string { + return lines.map(l => { + if (l.type === 'var') return `${l.key}=${l.value}` + return l.raw + }).join('\n') +} + +function getEnvPath(): string | null { + if (!config.openclawHome) return null + return join(config.openclawHome, '.env') +} + +async function readEnvFile(): Promise<{ lines: EnvLine[]; raw: string } | null> { + const envPath = getEnvPath() + if (!envPath) return null + try { + const raw = await readFile(envPath, 'utf-8') + return { lines: parseEnv(raw), raw } + } catch (err: any) { + if (err.code === 'ENOENT') return { lines: [], raw: '' } + throw err + } +} + +async function writeEnvFile(lines: EnvLine[]): Promise { + const envPath = getEnvPath()! + const tmpPath = envPath + '.tmp' + const content = serializeEnv(lines) + await writeFile(tmpPath, content, 'utf-8') + await rename(tmpPath, envPath) +} + +function redactValue(value: string): string { + if (value.length <= 4) return '****' + return '****' + value.slice(-4) +} + +function isVarBlocked(key: string): boolean { + if (BLOCKED_VARS.has(key)) return true + return BLOCKED_PREFIXES.some(p => key.startsWith(p)) +} + +// Uses execFileSync (no shell) to avoid command injection +function checkOpAvailable(): boolean { + try { + execFileSync('which', ['op'], { stdio: 'pipe', timeout: 3000 }) + return true + } catch { + return false + } +} + +/** + * Build env for op CLI. The OP_SERVICE_ACCOUNT_TOKEN may live in the + * OpenClaw .env (not the MC .env that systemd loads). Read it at + * runtime so the op CLI can authenticate. + */ +async function getOpEnv(): Promise { + const base: NodeJS.ProcessEnv = { ...process.env } + // Already in process env? Use it. + if (base.OP_SERVICE_ACCOUNT_TOKEN) return base + // Try reading from the OpenClaw .env + const envData = await readEnvFile() + if (envData) { + for (const line of envData.lines) { + if (line.type === 'var' && line.key === 'OP_SERVICE_ACCOUNT_TOKEN' && line.value) { + base.OP_SERVICE_ACCOUNT_TOKEN = line.value + break + } + } + } + return base +} + +// --------------------------------------------------------------------------- +// GET /api/integrations — list all integrations with status + redacted values +// --------------------------------------------------------------------------- + +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const envData = await readEnvFile() + if (!envData) { + return NextResponse.json({ error: 'OPENCLAW_HOME not configured' }, { status: 404 }) + } + + const envMap = new Map() + for (const line of envData.lines) { + if (line.type === 'var' && line.key) { + envMap.set(line.key, line.value!) + } + } + + const opAvailable = checkOpAvailable() + + const integrations = INTEGRATIONS.map(def => { + const vars: Record = {} + let allSet = true + let anySet = false + + for (const envVar of def.envVars) { + const val = envMap.get(envVar) + if (val && val.length > 0) { + vars[envVar] = { redacted: redactValue(val), set: true } + anySet = true + } else { + vars[envVar] = { redacted: '', set: false } + allSet = false + } + } + + const status = allSet && anySet ? 'connected' : anySet ? 'partial' : 'not_configured' + + return { + id: def.id, + name: def.name, + category: def.category, + categoryLabel: CATEGORIES[def.category]?.label ?? def.category, + envVars: vars, + status, + vaultItem: def.vaultItem ?? null, + testable: def.testable ?? false, + } + }) + + return NextResponse.json({ + integrations, + categories: Object.entries(CATEGORIES) + .sort(([, a], [, b]) => a.order - b.order) + .map(([id, meta]) => ({ id, label: meta.label })), + opAvailable, + envPath: getEnvPath(), + }) +} + +// --------------------------------------------------------------------------- +// PUT /api/integrations — update/add env vars +// Body: { vars: { KEY: "value", ... } } +// --------------------------------------------------------------------------- + +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const body = await request.json().catch(() => null) + if (!body?.vars || typeof body.vars !== 'object') { + return NextResponse.json({ error: 'vars object required' }, { status: 400 }) + } + + for (const key of Object.keys(body.vars)) { + if (isVarBlocked(key)) { + return NextResponse.json({ error: `Cannot set protected variable: ${key}` }, { status: 403 }) + } + if (!/^[A-Z_][A-Z0-9_]*$/i.test(key)) { + return NextResponse.json({ error: `Invalid variable name: ${key}` }, { status: 400 }) + } + } + + const envData = await readEnvFile() + if (!envData) { + return NextResponse.json({ error: 'OPENCLAW_HOME not configured' }, { status: 404 }) + } + + const { lines } = envData + const updatedKeys: string[] = [] + + for (const [key, value] of Object.entries(body.vars)) { + const strValue = String(value) + const existing = lines.find(l => l.type === 'var' && l.key === key) + + if (existing) { + existing.value = strValue + } else { + if (lines.length > 0 && lines[lines.length - 1].type !== 'blank') { + lines.push({ type: 'blank', raw: '' }) + } + lines.push({ type: 'var', raw: `${key}=${strValue}`, key, value: strValue }) + } + updatedKeys.push(key) + } + + await writeEnvFile(lines) + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'integrations_update', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { updated_keys: updatedKeys }, + ip_address: ipAddress, + }) + + return NextResponse.json({ updated: updatedKeys, count: updatedKeys.length }) +} + +// --------------------------------------------------------------------------- +// DELETE /api/integrations?keys=KEY1,KEY2 — remove env vars +// --------------------------------------------------------------------------- + +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const { searchParams } = new URL(request.url) + const keysParam = searchParams.get('keys') + if (!keysParam) { + return NextResponse.json({ error: 'keys parameter required (comma-separated)' }, { status: 400 }) + } + + const keysToRemove = new Set(keysParam.split(',').map(k => k.trim()).filter(Boolean)) + if (keysToRemove.size === 0) { + return NextResponse.json({ error: 'At least one key required' }, { status: 400 }) + } + + for (const key of keysToRemove) { + if (isVarBlocked(key)) { + return NextResponse.json({ error: `Cannot remove protected variable: ${key}` }, { status: 403 }) + } + } + + const envData = await readEnvFile() + if (!envData) { + return NextResponse.json({ error: 'OPENCLAW_HOME not configured' }, { status: 404 }) + } + + const removed: string[] = [] + const newLines = envData.lines.filter(l => { + if (l.type === 'var' && l.key && keysToRemove.has(l.key)) { + removed.push(l.key) + return false + } + return true + }) + + if (removed.length > 0) { + await writeEnvFile(newLines) + } + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'integrations_remove', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { removed_keys: removed }, + ip_address: ipAddress, + }) + + return NextResponse.json({ removed, count: removed.length }) +} + +// --------------------------------------------------------------------------- +// POST /api/integrations — action dispatcher (test, pull) +// Body: { action: "test"|"pull", integrationId: "..." } +// --------------------------------------------------------------------------- + +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const body = await request.json().catch(() => null) + if (!body?.action) { + return NextResponse.json({ error: 'action required' }, { status: 400 }) + } + + // pull-all is a batch action — no integrationId needed + if (body.action === 'pull-all') { + return handlePullAll(request, auth.user, body.category as string | undefined) + } + + if (!body.integrationId) { + return NextResponse.json({ error: 'integrationId required' }, { status: 400 }) + } + + const integration = INTEGRATIONS.find(i => i.id === body.integrationId) + if (!integration) { + return NextResponse.json({ error: `Unknown integration: ${body.integrationId}` }, { status: 404 }) + } + + if (body.action === 'test') { + return handleTest(integration, request, auth.user) + } + + if (body.action === 'pull') { + return handlePull(integration, request, auth.user) + } + + return NextResponse.json({ error: `Unknown action: ${body.action}` }, { status: 400 }) +} + +// --------------------------------------------------------------------------- +// Test connection for an integration +// --------------------------------------------------------------------------- + +async function handleTest( + integration: IntegrationDef, + request: NextRequest, + user: { username: string; id: number } +) { + if (!integration.testable) { + return NextResponse.json({ error: 'This integration does not support testing' }, { status: 400 }) + } + + const envData = await readEnvFile() + if (!envData) { + return NextResponse.json({ error: 'OPENCLAW_HOME not configured' }, { status: 404 }) + } + + const envMap = new Map() + for (const line of envData.lines) { + if (line.type === 'var' && line.key) envMap.set(line.key, line.value!) + } + + try { + let result: { ok: boolean; detail: string } + + switch (integration.id) { + case 'telegram_jarv': + case 'telegram_forge': + case 'telegram_nefes': + case 'telegram_ops': { + const token = envMap.get(integration.envVars[0]) + if (!token) return NextResponse.json({ ok: false, detail: 'Token not set' }) + const res = await fetch(`https://api.telegram.org/bot${token}/getMe`, { signal: AbortSignal.timeout(5000) }) + const data = await res.json() + result = data.ok + ? { ok: true, detail: `Bot: @${data.result.username}` } + : { ok: false, detail: data.description || 'Failed' } + break + } + + case 'github': { + const token = envMap.get('GITHUB_TOKEN') + if (!token) return NextResponse.json({ ok: false, detail: 'Token not set' }) + const res = await fetch('https://api.github.com/user', { + headers: { Authorization: `Bearer ${token}`, 'User-Agent': 'MissionControl/1.0' }, + signal: AbortSignal.timeout(5000), + }) + if (res.ok) { + const data = await res.json() + result = { ok: true, detail: `User: ${data.login}` } + } else { + result = { ok: false, detail: `HTTP ${res.status}` } + } + break + } + + case 'anthropic': { + const key = envMap.get('ANTHROPIC_API_KEY') + if (!key) return NextResponse.json({ ok: false, detail: 'API key not set' }) + const res = await fetch('https://api.anthropic.com/v1/models', { + method: 'GET', + headers: { 'x-api-key': key, 'anthropic-version': '2023-06-01' }, + signal: AbortSignal.timeout(5000), + }) + result = res.ok + ? { ok: true, detail: 'API key valid' } + : { ok: false, detail: `HTTP ${res.status}` } + break + } + + case 'openai': { + const key = envMap.get('OPENAI_API_KEY') + if (!key) return NextResponse.json({ ok: false, detail: 'API key not set' }) + const res = await fetch('https://api.openai.com/v1/models', { + headers: { Authorization: `Bearer ${key}` }, + signal: AbortSignal.timeout(5000), + }) + result = res.ok + ? { ok: true, detail: 'API key valid' } + : { ok: false, detail: `HTTP ${res.status}` } + break + } + + case 'openrouter': { + const key = envMap.get('OPENROUTER_API_KEY') + if (!key) return NextResponse.json({ ok: false, detail: 'API key not set' }) + const res = await fetch('https://openrouter.ai/api/v1/models', { + headers: { Authorization: `Bearer ${key}` }, + signal: AbortSignal.timeout(5000), + }) + result = res.ok + ? { ok: true, detail: 'API key valid' } + : { ok: false, detail: `HTTP ${res.status}` } + break + } + + default: + return NextResponse.json({ error: 'Test not implemented for this integration' }, { status: 400 }) + } + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'integration_test', + actor: user.username, + actor_id: user.id, + detail: { integration: integration.id, result: result.ok ? 'success' : 'failed' }, + ip_address: ipAddress, + }) + + return NextResponse.json(result) + } catch (err: any) { + return NextResponse.json({ ok: false, detail: err.message || 'Connection failed' }) + } +} + +// --------------------------------------------------------------------------- +// Pull value from 1Password vault — uses execFileSync (no shell) for safety +// --------------------------------------------------------------------------- + +async function handlePull( + integration: IntegrationDef, + request: NextRequest, + user: { username: string; id: number } +) { + if (!integration.vaultItem) { + return NextResponse.json({ error: 'No vault item configured for this integration' }, { status: 400 }) + } + + if (!checkOpAvailable()) { + return NextResponse.json({ error: '1Password CLI (op) is not installed' }, { status: 400 }) + } + + try { + const opEnv = await getOpEnv() + if (!opEnv.OP_SERVICE_ACCOUNT_TOKEN) { + return NextResponse.json({ error: 'OP_SERVICE_ACCOUNT_TOKEN not found in environment or .env' }, { status: 400 }) + } + + // execFileSync passes args as array — no shell interpolation possible + const secret = execFileSync('op', [ + 'item', 'get', integration.vaultItem, + '--vault', process.env.OP_VAULT_NAME || 'default', + '--fields', 'password', + '--format', 'json', + ], { timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env: opEnv }).toString().trim() + + let value: string + try { + const parsed = JSON.parse(secret) + value = parsed.value || parsed + } catch { + value = secret + } + + if (!value || value.length === 0) { + return NextResponse.json({ error: 'Empty value returned from 1Password' }, { status: 400 }) + } + + // Write to .env + const envData = await readEnvFile() + if (!envData) { + return NextResponse.json({ error: 'OPENCLAW_HOME not configured' }, { status: 404 }) + } + + const { lines } = envData + const envVar = integration.envVars[0] + + const existing = lines.find(l => l.type === 'var' && l.key === envVar) + if (existing) { + existing.value = value + } else { + if (lines.length > 0 && lines[lines.length - 1].type !== 'blank') { + lines.push({ type: 'blank', raw: '' }) + } + lines.push({ type: 'var', raw: `${envVar}=${value}`, key: envVar, value }) + } + + await writeEnvFile(lines) + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'integration_pull_1password', + actor: user.username, + actor_id: user.id, + detail: { integration: integration.id, env_var: envVar }, + ip_address: ipAddress, + }) + + return NextResponse.json({ + ok: true, + detail: `Pulled ${envVar} from 1Password`, + redacted: redactValue(value), + }) + } catch (err: any) { + return NextResponse.json({ + error: `1Password pull failed: ${err.message}`, + }, { status: 500 }) + } +} + +// --------------------------------------------------------------------------- +// Pull ALL vault-backed integrations from 1Password (optionally by category) +// --------------------------------------------------------------------------- + +async function handlePullAll( + request: NextRequest, + user: { username: string; id: number }, + category?: string, +) { + if (!checkOpAvailable()) { + return NextResponse.json({ error: '1Password CLI (op) is not installed' }, { status: 400 }) + } + + const opEnv = await getOpEnv() + if (!opEnv.OP_SERVICE_ACCOUNT_TOKEN) { + return NextResponse.json({ error: 'OP_SERVICE_ACCOUNT_TOKEN not found in environment or .env' }, { status: 400 }) + } + + const targets = INTEGRATIONS.filter(i => { + if (!i.vaultItem) return false + if (category && i.category !== category) return false + return true + }) + + if (targets.length === 0) { + return NextResponse.json({ error: 'No vault-backed integrations found for this category' }, { status: 400 }) + } + + const envData = await readEnvFile() + if (!envData) { + return NextResponse.json({ error: 'OPENCLAW_HOME not configured' }, { status: 404 }) + } + + const { lines } = envData + const results: { id: string; envVar: string; ok: boolean; detail: string }[] = [] + + for (const integration of targets) { + const envVar = integration.envVars[0] + try { + const secret = execFileSync('op', [ + 'item', 'get', integration.vaultItem!, + '--vault', process.env.OP_VAULT_NAME || 'default', + '--fields', 'password', + '--format', 'json', + ], { timeout: 15000, stdio: ['pipe', 'pipe', 'pipe'], env: opEnv }).toString().trim() + + let value: string + try { + const parsed = JSON.parse(secret) + value = parsed.value || parsed + } catch { + value = secret + } + + if (!value || value.length === 0) { + results.push({ id: integration.id, envVar, ok: false, detail: 'Empty value' }) + continue + } + + // Upsert into lines + const existing = lines.find(l => l.type === 'var' && l.key === envVar) + if (existing) { + existing.value = value + } else { + if (lines.length > 0 && lines[lines.length - 1].type !== 'blank') { + lines.push({ type: 'blank', raw: '' }) + } + lines.push({ type: 'var', raw: `${envVar}=${value}`, key: envVar, value }) + } + + results.push({ id: integration.id, envVar, ok: true, detail: `Pulled ${envVar}` }) + } catch (err: any) { + results.push({ id: integration.id, envVar, ok: false, detail: err.message || 'Failed' }) + } + } + + // Write .env once after all pulls + const successCount = results.filter(r => r.ok).length + if (successCount > 0) { + await writeEnvFile(lines) + } + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'integration_pull_all_1password', + actor: user.username, + actor_id: user.id, + detail: { + category: category ?? 'all', + success: successCount, + failed: results.length - successCount, + results: results.map(r => ({ id: r.id, ok: r.ok })), + }, + ip_address: ipAddress, + }) + + return NextResponse.json({ + ok: successCount > 0, + detail: `Pulled ${successCount}/${results.length} integrations`, + results, + }) +} diff --git a/src/app/api/logs/route.ts b/src/app/api/logs/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..c7008ff659b7fd3ed650aeda630ada0bdf0247fc --- /dev/null +++ b/src/app/api/logs/route.ts @@ -0,0 +1,279 @@ +import { NextRequest, NextResponse } from 'next/server' +import { readFile, readdir, stat } from 'fs/promises' +import { join } from 'path' +import { config } from '@/lib/config' +import { requireRole } from '@/lib/auth' + +const LOGS_PATH = config.logsDir + +interface LogEntry { + id: string + timestamp: number + level: 'info' | 'warn' | 'error' | 'debug' + source: string + session?: string + message: string + data?: any +} + +/** + * Parse a log line from various OpenClaw log formats: + * - Pipe-delimited: "2026-02-09T17:00:01+01:00|MONITOR|Consistency check completed" + * - Simple text: "done report=/path/to/.openclaw/workspace-/reports/..." + * - JSON structured: { timestamp, level, message, ... } + * - Gateway journal: "2026-02-09T18:05:49+01:00 host openclaw[1737454]: ..." + */ +function parseLogLine(line: string, source: string): LogEntry | null { + if (!line.trim()) return null + + try { + // Try JSON first + if (line.startsWith('{')) { + const parsed = JSON.parse(line) + return { + id: `${source}-${parsed.timestamp || Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + timestamp: parsed.timestamp || Date.now(), + level: parsed.level || 'info', + source: parsed.source || source, + session: parsed.session, + message: parsed.message || line, + data: parsed.data, + } + } + + // Pipe-delimited format: "TIMESTAMP|LEVEL|MESSAGE" + const pipeMatch = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:]+[^\|]*)\|([^\|]+)\|(.+)$/) + if (pipeMatch) { + const ts = new Date(pipeMatch[1]).getTime() + const levelRaw = pipeMatch[2].trim().toLowerCase() + let level: LogEntry['level'] = 'info' + if (levelRaw === 'error' || levelRaw === 'err') level = 'error' + else if (levelRaw === 'warn' || levelRaw === 'warning') level = 'warn' + else if (levelRaw === 'debug') level = 'debug' + else if (levelRaw === 'ok' || levelRaw === 'monitor' || levelRaw === 'info') level = 'info' + + return { + id: `${source}-${ts}-${Math.random().toString(36).slice(2, 8)}`, + timestamp: isNaN(ts) ? Date.now() : ts, + level, + source, + message: pipeMatch[3].trim(), + } + } + + // Gateway journal format: "TIMESTAMP HOSTNAME openclaw[PID]: MESSAGE" + const journalMatch = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:]+[^\s]*)\s+\S+\s+\S+:\s+(.+)$/) + if (journalMatch) { + const ts = new Date(journalMatch[1]).getTime() + const msg = journalMatch[2] + let level: LogEntry['level'] = 'info' + if (msg.includes('error') || msg.includes('Error') || msg.includes('ERR')) level = 'error' + else if (msg.includes('warn') || msg.includes('WARN')) level = 'warn' + else if (msg.includes('debug') || msg.includes('DEBUG')) level = 'debug' + + return { + id: `${source}-${ts}-${Math.random().toString(36).slice(2, 8)}`, + timestamp: isNaN(ts) ? Date.now() : ts, + level, + source, + message: msg, + } + } + + // ISO timestamp prefix: "2026-02-09T... [LEVEL] message" + const isoMatch = line.match(/^(\d{4}-\d{2}-\d{2}[\sT]\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:?\d{2})?)/) + const levelMatch = line.match(/\[(ERROR|WARN|INFO|DEBUG)\]/i) || line.match(/(ERROR|WARN|INFO|DEBUG):/i) + + let timestamp = Date.now() + if (isoMatch) { + const t = new Date(isoMatch[1]).getTime() + if (!isNaN(t)) timestamp = t + } + + let level: LogEntry['level'] = 'info' + if (levelMatch) { + level = levelMatch[1].toLowerCase() as LogEntry['level'] + } else if (line.toLowerCase().includes('error')) { + level = 'error' + } else if (line.toLowerCase().includes('warn')) { + level = 'warn' + } + + return { + id: `${source}-${timestamp}-${Math.random().toString(36).slice(2, 8)}`, + timestamp, + level, + source, + message: line.trim(), + } + } catch { + return { + id: `${source}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + timestamp: Date.now(), + level: 'info', + source, + message: line.trim(), + } + } +} + +/** + * Discover all log files in the OpenClaw logs directory. + * Scans both top-level and automation/ subdirectory. + */ +async function discoverLogFiles(): Promise> { + const files: Array<{ path: string; source: string }> = [] + if (!LOGS_PATH) return files + + try { + const entries = await readdir(LOGS_PATH, { withFileTypes: true }) + for (const entry of entries) { + if (entry.isFile() && entry.name.endsWith('.log')) { + files.push({ + path: join(LOGS_PATH, entry.name), + source: entry.name.replace('.log', ''), + }) + } else if (entry.isDirectory()) { + // Scan subdirectories (e.g., automation/) + try { + const subEntries = await readdir(join(LOGS_PATH, entry.name)) + for (const subFile of subEntries) { + if (subFile.endsWith('.log')) { + files.push({ + path: join(LOGS_PATH, entry.name, subFile), + source: `${entry.name}/${subFile.replace('.log', '')}`, + }) + } + } + } catch { + // Skip unreadable subdirectories + } + } + } + } catch { + // Logs directory doesn't exist or isn't readable + } + + return files +} + +async function readLogFile(filePath: string, source: string, maxLines: number): Promise { + try { + const content = await readFile(filePath, 'utf-8') + const lines = content.split('\n').slice(-maxLines) + const entries: LogEntry[] = [] + + for (const line of lines) { + const entry = parseLogLine(line, source) + if (entry) entries.push(entry) + } + + return entries + } catch { + return [] + } +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') || 'recent' + const limit = parseInt(searchParams.get('limit') || '100') + const level = searchParams.get('level') + const session = searchParams.get('session') + const search = searchParams.get('search') + const source = searchParams.get('source') + + if (action === 'recent') { + const logFiles = await discoverLogFiles() + let logs: LogEntry[] = [] + + // Read from all discovered log files + for (const file of logFiles) { + if (source && file.source !== source) continue + const entries = await readLogFile(file.path, file.source, 200) + logs.push(...entries) + } + + // Sort newest first + logs.sort((a, b) => b.timestamp - a.timestamp) + + // Apply filters + if (level) { + logs = logs.filter(log => log.level === level) + } + if (session) { + logs = logs.filter(log => log.session?.includes(session)) + } + if (search) { + const searchLower = search.toLowerCase() + logs = logs.filter(log => + log.message.toLowerCase().includes(searchLower) || + log.source.toLowerCase().includes(searchLower) + ) + } + + logs = logs.slice(0, limit) + return NextResponse.json({ logs }) + } + + if (action === 'sources') { + const logFiles = await discoverLogFiles() + const sources = logFiles.map(f => f.source) + return NextResponse.json({ sources }) + } + + if (action === 'tail') { + const sinceTimestamp = parseInt(searchParams.get('since') || '0') + const logFiles = await discoverLogFiles() + let logs: LogEntry[] = [] + + for (const file of logFiles) { + if (source && file.source !== source) continue + const entries = await readLogFile(file.path, file.source, 50) + logs.push(...entries.filter(e => e.timestamp > sinceTimestamp)) + } + + logs.sort((a, b) => b.timestamp - a.timestamp) + logs = logs.slice(0, limit) + return NextResponse.json({ logs }) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Logs API error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const { action, message, level, source: customSource, session } = await request.json() + + if (action === 'add') { + if (!message) { + return NextResponse.json({ error: 'Message required' }, { status: 400 }) + } + + const logEntry: LogEntry = { + id: `custom-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + timestamp: Date.now(), + level: level || 'info', + source: customSource || 'mission-control', + session, + message, + data: null, + } + + return NextResponse.json({ success: true, entry: logEntry }) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Logs API error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/src/app/api/memory/route.ts b/src/app/api/memory/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..fdc83270a21a16e195172060f375364079169db9 --- /dev/null +++ b/src/app/api/memory/route.ts @@ -0,0 +1,325 @@ +import { NextRequest, NextResponse } from 'next/server' +import { readdir, readFile, stat, lstat, realpath, writeFile, mkdir, unlink } from 'fs/promises' +import { join, dirname, sep } from 'path' +import { config } from '@/lib/config' +import { resolveWithin } from '@/lib/paths' +import { requireRole } from '@/lib/auth' + +const MEMORY_PATH = config.memoryDir + +interface MemoryFile { + path: string + name: string + type: 'file' | 'directory' + size?: number + modified?: number + children?: MemoryFile[] +} + +function isWithinBase(base: string, candidate: string): boolean { + if (candidate === base) return true + return candidate.startsWith(base + sep) +} + +async function resolveSafeMemoryPath(baseDir: string, relativePath: string): Promise { + const baseReal = await realpath(baseDir) + const fullPath = resolveWithin(baseDir, relativePath) + + // For non-existent paths, validate containment using the parent directory realpath. + // This also blocks symlinked parent segments that escape the base. + let parentReal: string + try { + parentReal = await realpath(dirname(fullPath)) + } catch (err) { + const code = (err as NodeJS.ErrnoException).code + if (code === 'ENOENT') { + throw new Error('Parent directory not found') + } + throw err + } + if (!isWithinBase(baseReal, parentReal)) { + throw new Error('Path escapes base directory (symlink)') + } + + // If the file exists, ensure it also resolves within base and is not a symlink. + try { + const st = await lstat(fullPath) + if (st.isSymbolicLink()) { + throw new Error('Symbolic links are not allowed') + } + const fileReal = await realpath(fullPath) + if (!isWithinBase(baseReal, fileReal)) { + throw new Error('Path escapes base directory (symlink)') + } + } catch (err) { + const code = (err as NodeJS.ErrnoException).code + if (code !== 'ENOENT') { + throw err + } + } + + return fullPath +} + +async function buildFileTree(dirPath: string, relativePath: string = ''): Promise { + try { + const items = await readdir(dirPath, { withFileTypes: true }) + const files: MemoryFile[] = [] + + for (const item of items) { + if (item.isSymbolicLink()) { + continue + } + const itemPath = join(dirPath, item.name) + const itemRelativePath = join(relativePath, item.name) + + try { + const stats = await stat(itemPath) + + if (item.isDirectory()) { + const children = await buildFileTree(itemPath, itemRelativePath) + files.push({ + path: itemRelativePath, + name: item.name, + type: 'directory', + modified: stats.mtime.getTime(), + children + }) + } else if (item.isFile()) { + files.push({ + path: itemRelativePath, + name: item.name, + type: 'file', + size: stats.size, + modified: stats.mtime.getTime() + }) + } + } catch (error) { + console.error(`Error reading ${itemPath}:`, error) + } + } + + return files.sort((a, b) => { + // Directories first, then files, alphabetical within each type + if (a.type !== b.type) { + return a.type === 'directory' ? -1 : 1 + } + return a.name.localeCompare(b.name) + }) + } catch (error) { + console.error(`Error reading directory ${dirPath}:`, error) + return [] + } +} + +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'viewer') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const { searchParams } = new URL(request.url) + const path = searchParams.get('path') + const action = searchParams.get('action') + + if (action === 'tree') { + // Return the file tree + if (!MEMORY_PATH) { + return NextResponse.json({ tree: [] }) + } + const tree = await buildFileTree(MEMORY_PATH) + return NextResponse.json({ tree }) + } + + if (action === 'content' && path) { + // Return file content + if (!MEMORY_PATH) { + return NextResponse.json({ error: 'Memory directory not configured' }, { status: 500 }) + } + const fullPath = await resolveSafeMemoryPath(MEMORY_PATH, path) + + try { + const content = await readFile(fullPath, 'utf-8') + const stats = await stat(fullPath) + + return NextResponse.json({ + content, + size: stats.size, + modified: stats.mtime.getTime(), + path + }) + } catch (error) { + return NextResponse.json({ error: 'File not found' }, { status: 404 }) + } + } + + if (action === 'search') { + const query = searchParams.get('query') + if (!query) { + return NextResponse.json({ error: 'Query required' }, { status: 400 }) + } + if (!MEMORY_PATH) { + return NextResponse.json({ query, results: [] }) + } + + // Simple file search - in production you'd want a more sophisticated search + const results: Array<{path: string, name: string, matches: number}> = [] + + const searchInFile = async (filePath: string, relativePath: string) => { + try { + const st = await stat(filePath) + // Avoid large-file scanning and memory blowups. + if (st.size > 1_000_000) { + return + } + const content = await readFile(filePath, 'utf-8') + const haystack = content.toLowerCase() + const needle = query.toLowerCase() + if (!needle) return + let matches = 0 + let idx = haystack.indexOf(needle) + while (idx !== -1) { + matches += 1 + idx = haystack.indexOf(needle, idx + needle.length) + } + + if (matches > 0) { + results.push({ + path: relativePath, + name: relativePath.split('/').pop() || '', + matches + }) + } + } catch (error) { + // Skip files that can't be read + } + } + + const searchDirectory = async (dirPath: string, relativePath: string = '') => { + try { + const items = await readdir(dirPath, { withFileTypes: true }) + + for (const item of items) { + if (item.isSymbolicLink()) { + continue + } + const itemPath = join(dirPath, item.name) + const itemRelativePath = join(relativePath, item.name) + + if (item.isDirectory()) { + await searchDirectory(itemPath, itemRelativePath) + } else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.txt'))) { + await searchInFile(itemPath, itemRelativePath) + } + } + } catch (error) { + console.error(`Error searching directory ${dirPath}:`, error) + } + } + + await searchDirectory(MEMORY_PATH) + + return NextResponse.json({ + query, + results: results.sort((a, b) => b.matches - a.matches) + }) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Memory API error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const body = await request.json() + const { action, path, content } = body + + if (!path) { + return NextResponse.json({ error: 'Path is required' }, { status: 400 }) + } + + if (!MEMORY_PATH) { + return NextResponse.json({ error: 'Memory directory not configured' }, { status: 500 }) + } + const fullPath = await resolveSafeMemoryPath(MEMORY_PATH, path) + + if (action === 'save') { + // Save file content + if (content === undefined) { + return NextResponse.json({ error: 'Content is required for save action' }, { status: 400 }) + } + + await writeFile(fullPath, content, 'utf-8') + return NextResponse.json({ success: true, message: 'File saved successfully' }) + } + + if (action === 'create') { + // Create new file + const dirPath = dirname(fullPath) + + // Ensure directory exists + try { + await mkdir(dirPath, { recursive: true }) + } catch (error) { + // Directory might already exist + } + + // Check if file already exists + try { + await stat(fullPath) + return NextResponse.json({ error: 'File already exists' }, { status: 409 }) + } catch (error) { + // File doesn't exist, which is what we want + } + + await writeFile(fullPath, content || '', 'utf-8') + return NextResponse.json({ success: true, message: 'File created successfully' }) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Memory POST API error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} + +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const body = await request.json() + const { action, path } = body + + if (!path) { + return NextResponse.json({ error: 'Path is required' }, { status: 400 }) + } + + if (!MEMORY_PATH) { + return NextResponse.json({ error: 'Memory directory not configured' }, { status: 500 }) + } + const fullPath = await resolveSafeMemoryPath(MEMORY_PATH, path) + + if (action === 'delete') { + // Check if file exists + try { + await stat(fullPath) + } catch (error) { + return NextResponse.json({ error: 'File not found' }, { status: 404 }) + } + + await unlink(fullPath) + return NextResponse.json({ success: true, message: 'File deleted successfully' }) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Memory DELETE API error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/src/app/api/notifications/deliver/route.ts b/src/app/api/notifications/deliver/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb0949a09980da2dda8b2edcf0a6a6c488018d28 --- /dev/null +++ b/src/app/api/notifications/deliver/route.ts @@ -0,0 +1,279 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, Notification, db_helpers } from '@/lib/db'; +import { runOpenClaw } from '@/lib/command'; +import { requireRole } from '@/lib/auth'; + +/** + * POST /api/notifications/deliver - Notification delivery daemon endpoint + * + * Polls undelivered notifications and sends them to agent sessions + * via OpenClaw sessions_send command + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const body = await request.json(); + const { + agent_filter, // Optional: only deliver to specific agent + limit = 50, // Max notifications to process per call + dry_run = false // Test mode - don't actually deliver + } = body; + + // Get undelivered notifications + let query = ` + SELECT n.*, a.session_key + FROM notifications n + LEFT JOIN agents a ON n.recipient = a.name + WHERE n.delivered_at IS NULL + `; + + const params: any[] = []; + + if (agent_filter) { + query += ' AND n.recipient = ?'; + params.push(agent_filter); + } + + query += ' ORDER BY n.created_at ASC LIMIT ?'; + params.push(limit); + + const undeliveredNotifications = db.prepare(query).all(...params) as (Notification & { session_key?: string })[]; + + if (undeliveredNotifications.length === 0) { + return NextResponse.json({ + status: 'success', + message: 'No undelivered notifications found', + processed: 0, + delivered: 0, + errors: [] + }); + } + + let deliveredCount = 0; + let errorCount = 0; + const errors: any[] = []; + const deliveryResults: any[] = []; + + for (const notification of undeliveredNotifications) { + try { + // Skip if agent doesn't have session key + if (!notification.session_key) { + errors.push({ + notification_id: notification.id, + recipient: notification.recipient, + error: 'Agent has no session key configured' + }); + errorCount++; + continue; + } + + // Format message for delivery + const message = formatNotificationMessage(notification); + + if (!dry_run) { + // Send notification via OpenClaw sessions_send + try { + const { stdout, stderr } = await runOpenClaw( + [ + 'gateway', + 'sessions_send', + '--session', + notification.session_key, + '--message', + message + ], + { timeoutMs: 10000 } + ); + + if (stderr && stderr.includes('error')) { + throw new Error(`OpenClaw error: ${stderr}`); + } + + // Mark as delivered + const now = Math.floor(Date.now() / 1000); + db.prepare('UPDATE notifications SET delivered_at = ? WHERE id = ?') + .run(now, notification.id); + + deliveredCount++; + deliveryResults.push({ + notification_id: notification.id, + recipient: notification.recipient, + session_key: notification.session_key, + delivered_at: now, + status: 'delivered', + stdout: stdout.substring(0, 200) // Truncate for storage + }); + + // Log successful delivery + db_helpers.logActivity( + 'notification_delivered', + 'notification', + notification.id, + 'system', + `Notification delivered to ${notification.recipient}`, + { + notification_type: notification.type, + session_key: notification.session_key, + title: notification.title + } + ); + } catch (cmdError: any) { + throw new Error(`Command failed: ${cmdError.message}`); + } + } else { + // Dry run - just log what would be sent + deliveryResults.push({ + notification_id: notification.id, + recipient: notification.recipient, + session_key: notification.session_key, + status: 'dry_run', + message: message + }); + deliveredCount++; + } + } catch (error: any) { + errorCount++; + errors.push({ + notification_id: notification.id, + recipient: notification.recipient, + error: error.message + }); + + console.error(`Failed to deliver notification ${notification.id} to ${notification.recipient}:`, error); + } + } + + // Log delivery batch summary + db_helpers.logActivity( + 'notification_delivery_batch', + 'system', + 0, + 'notification_daemon', + `Processed ${undeliveredNotifications.length} notifications: ${deliveredCount} delivered, ${errorCount} failed`, + { + total_processed: undeliveredNotifications.length, + delivered: deliveredCount, + errors: errorCount, + dry_run, + agent_filter: agent_filter || null + } + ); + + return NextResponse.json({ + status: 'success', + message: `Processed ${undeliveredNotifications.length} notifications`, + total_processed: undeliveredNotifications.length, + delivered: deliveredCount, + errors: errorCount, + dry_run, + delivery_results: deliveryResults, + error_details: errors + }); + } catch (error) { + console.error('POST /api/notifications/deliver error:', error); + return NextResponse.json({ error: 'Failed to deliver notifications' }, { status: 500 }); + } +} + +/** + * GET /api/notifications/deliver - Get delivery status and statistics + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + const agent = searchParams.get('agent'); + + // Get delivery statistics + let baseQuery = 'SELECT COUNT(*) as count FROM notifications'; + let params: any[] = []; + + if (agent) { + baseQuery += ' WHERE recipient = ?'; + params.push(agent); + } + + const totalNotifications = db.prepare(baseQuery).get(...params) as { count: number }; + + const undeliveredCount = db.prepare( + baseQuery + (agent ? ' AND' : ' WHERE') + ' delivered_at IS NULL' + ).get(...params) as { count: number }; + + const deliveredCount = db.prepare( + baseQuery + (agent ? ' AND' : ' WHERE') + ' delivered_at IS NOT NULL' + ).get(...params) as { count: number }; + + // Get recent delivery activity + const recentDeliveries = db.prepare(` + SELECT + recipient, + type, + title, + delivered_at, + created_at + FROM notifications + WHERE delivered_at IS NOT NULL + ${agent ? 'AND recipient = ?' : ''} + ORDER BY delivered_at DESC + LIMIT 10 + `).all(...(agent ? [agent] : [])); + + // Get agents with pending notifications + const agentsPending = db.prepare(` + SELECT + n.recipient, + a.session_key, + COUNT(*) as pending_count + FROM notifications n + LEFT JOIN agents a ON n.recipient = a.name + WHERE n.delivered_at IS NULL + GROUP BY n.recipient, a.session_key + ORDER BY pending_count DESC + `).all() as any[]; + + return NextResponse.json({ + statistics: { + total: totalNotifications.count, + delivered: deliveredCount.count, + undelivered: undeliveredCount.count, + delivery_rate: totalNotifications.count > 0 ? + Math.round((deliveredCount.count / totalNotifications.count) * 100) : 0 + }, + agents_with_pending: agentsPending, + recent_deliveries: recentDeliveries, + agent_filter: agent + }); + } catch (error) { + console.error('GET /api/notifications/deliver error:', error); + return NextResponse.json({ error: 'Failed to get delivery status' }, { status: 500 }); + } +} + +/** + * Format notification for delivery to agent session + */ +function formatNotificationMessage(notification: Notification): string { + const timestamp = new Date(notification.created_at * 1000).toLocaleString(); + + let message = `🔔 **${notification.title}**\n\n`; + message += `${notification.message}\n\n`; + + if (notification.type === 'mention') { + message += `📝 You were mentioned in a comment\n`; + } else if (notification.type === 'assignment') { + message += `📋 You have been assigned a new task\n`; + } else if (notification.type === 'due_date') { + message += `⏰ Task deadline approaching\n`; + } + + if (notification.source_type && notification.source_id) { + message += `🔗 Related ${notification.source_type} ID: ${notification.source_id}\n`; + } + + message += `⏰ ${timestamp}`; + + return message; +} diff --git a/src/app/api/notifications/route.ts b/src/app/api/notifications/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..41bbbb084ed9c9288f1b9a8ad0d245f31ed72822 --- /dev/null +++ b/src/app/api/notifications/route.ts @@ -0,0 +1,264 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, Notification } from '@/lib/db'; +import { requireRole } from '@/lib/auth'; + +/** + * GET /api/notifications - Get notifications for a specific recipient + * Query params: recipient, unread_only, type, limit, offset + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + + // Parse query parameters + const recipient = searchParams.get('recipient'); + const unread_only = searchParams.get('unread_only') === 'true'; + const type = searchParams.get('type'); + const limit = parseInt(searchParams.get('limit') || '50'); + const offset = parseInt(searchParams.get('offset') || '0'); + + if (!recipient) { + return NextResponse.json({ error: 'Recipient is required' }, { status: 400 }); + } + + // Build dynamic query + let query = 'SELECT * FROM notifications WHERE recipient = ?'; + const params: any[] = [recipient]; + + if (unread_only) { + query += ' AND read_at IS NULL'; + } + + if (type) { + query += ' AND type = ?'; + params.push(type); + } + + query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; + params.push(limit, offset); + + const stmt = db.prepare(query); + const notifications = stmt.all(...params) as Notification[]; + + // Enhance notifications with related entity data + const enhancedNotifications = notifications.map(notification => { + let sourceDetails = null; + + try { + if (notification.source_type && notification.source_id) { + switch (notification.source_type) { + case 'task': + const task = db.prepare('SELECT id, title, status FROM tasks WHERE id = ?').get(notification.source_id) as any; + if (task) { + sourceDetails = { type: 'task', ...task }; + } + break; + + case 'comment': + const comment = db.prepare(` + SELECT c.id, c.content, c.task_id, t.title as task_title + FROM comments c + LEFT JOIN tasks t ON c.task_id = t.id + WHERE c.id = ? + `).get(notification.source_id) as any; + if (comment) { + sourceDetails = { + type: 'comment', + ...comment, + content_preview: comment.content?.substring(0, 100) || '' + }; + } + break; + + case 'agent': + const agent = db.prepare('SELECT id, name, role, status FROM agents WHERE id = ?').get(notification.source_id) as any; + if (agent) { + sourceDetails = { type: 'agent', ...agent }; + } + break; + } + } + } catch (error) { + console.warn(`Failed to fetch source details for notification ${notification.id}:`, error); + } + + return { + ...notification, + source: sourceDetails + }; + }); + + // Get unread count for this recipient + const unreadCount = db.prepare(` + SELECT COUNT(*) as count + FROM notifications + WHERE recipient = ? AND read_at IS NULL + `).get(recipient) as { count: number }; + + return NextResponse.json({ + notifications: enhancedNotifications, + total: notifications.length, + unreadCount: unreadCount.count + }); + } catch (error) { + console.error('GET /api/notifications error:', error); + return NextResponse.json({ error: 'Failed to fetch notifications' }, { status: 500 }); + } +} + +/** + * PUT /api/notifications - Mark notifications as read + * Body: { ids: number[] } or { recipient: string } (mark all as read) + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const body = await request.json(); + const { ids, recipient, markAllRead } = body; + + const now = Math.floor(Date.now() / 1000); + + if (markAllRead && recipient) { + // Mark all notifications as read for this recipient + const stmt = db.prepare(` + UPDATE notifications + SET read_at = ? + WHERE recipient = ? AND read_at IS NULL + `); + + const result = stmt.run(now, recipient); + + return NextResponse.json({ + success: true, + markedAsRead: result.changes + }); + } else if (ids && Array.isArray(ids)) { + // Mark specific notifications as read + const placeholders = ids.map(() => '?').join(','); + const stmt = db.prepare(` + UPDATE notifications + SET read_at = ? + WHERE id IN (${placeholders}) AND read_at IS NULL + `); + + const result = stmt.run(now, ...ids); + + return NextResponse.json({ + success: true, + markedAsRead: result.changes + }); + } else { + return NextResponse.json({ + error: 'Either provide ids array or recipient with markAllRead=true' + }, { status: 400 }); + } + } catch (error) { + console.error('PUT /api/notifications error:', error); + return NextResponse.json({ error: 'Failed to update notifications' }, { status: 500 }); + } +} + +/** + * DELETE /api/notifications - Delete notifications + * Body: { ids: number[] } or { recipient: string, olderThan: number } + */ +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'admin'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const body = await request.json(); + const { ids, recipient, olderThan } = body; + + if (ids && Array.isArray(ids)) { + // Delete specific notifications + const placeholders = ids.map(() => '?').join(','); + const stmt = db.prepare(` + DELETE FROM notifications + WHERE id IN (${placeholders}) + `); + + const result = stmt.run(...ids); + + return NextResponse.json({ + success: true, + deleted: result.changes + }); + } else if (recipient && olderThan) { + // Delete old notifications for recipient + const stmt = db.prepare(` + DELETE FROM notifications + WHERE recipient = ? AND created_at < ? + `); + + const result = stmt.run(recipient, olderThan); + + return NextResponse.json({ + success: true, + deleted: result.changes + }); + } else { + return NextResponse.json({ + error: 'Either provide ids array or recipient with olderThan timestamp' + }, { status: 400 }); + } + } catch (error) { + console.error('DELETE /api/notifications error:', error); + return NextResponse.json({ error: 'Failed to delete notifications' }, { status: 500 }); + } +} + +/** + * POST /api/notifications/mark-delivered - Mark notifications as delivered to agent + * Body: { agent: string } + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const body = await request.json(); + const { agent, action } = body; + + if (action === 'mark-delivered') { + if (!agent) { + return NextResponse.json({ error: 'Agent name is required' }, { status: 400 }); + } + + const now = Math.floor(Date.now() / 1000); + + // Mark undelivered notifications as delivered + const stmt = db.prepare(` + UPDATE notifications + SET delivered_at = ? + WHERE recipient = ? AND delivered_at IS NULL + `); + + const result = stmt.run(now, agent); + + // Get the notifications that were just marked as delivered + const deliveredNotifications = db.prepare(` + SELECT * FROM notifications + WHERE recipient = ? AND delivered_at = ? + ORDER BY created_at DESC + `).all(agent, now) as Notification[]; + + return NextResponse.json({ + success: true, + delivered: result.changes, + notifications: deliveredNotifications + }); + } else { + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }); + } + } catch (error) { + console.error('POST /api/notifications error:', error); + return NextResponse.json({ error: 'Failed to process notification action' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/src/app/api/pipelines/route.ts b/src/app/api/pipelines/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..c63fe7349511d4c82e69c66e555b66bd43cbecd5 --- /dev/null +++ b/src/app/api/pipelines/route.ts @@ -0,0 +1,177 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers } from '@/lib/db' +import { requireRole } from '@/lib/auth' + +export interface PipelineStep { + template_id: number + template_name?: string + on_failure: 'stop' | 'continue' +} + +export interface Pipeline { + id: number + name: string + description: string | null + steps: string // JSON array of PipelineStep + created_by: string + created_at: number + updated_at: number + use_count: number + last_used_at: number | null +} + +/** + * GET /api/pipelines - List all pipelines with enriched step data + */ +export async function GET() { + try { + const db = getDatabase() + const pipelines = db.prepare( + 'SELECT * FROM workflow_pipelines ORDER BY use_count DESC, updated_at DESC' + ).all() as Pipeline[] + + // Enrich steps with template names + const templates = db.prepare('SELECT id, name FROM workflow_templates').all() as Array<{ id: number; name: string }> + const nameMap = new Map(templates.map(t => [t.id, t.name])) + + // Get run counts per pipeline + const runCounts = db.prepare(` + SELECT pipeline_id, COUNT(*) as total, + SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed, + SUM(CASE WHEN status = 'failed' THEN 1 ELSE 0 END) as failed, + SUM(CASE WHEN status = 'running' THEN 1 ELSE 0 END) as running + FROM pipeline_runs GROUP BY pipeline_id + `).all() as Array<{ pipeline_id: number; total: number; completed: number; failed: number; running: number }> + const runMap = new Map(runCounts.map(r => [r.pipeline_id, r])) + + const parsed = pipelines.map(p => { + const steps: PipelineStep[] = JSON.parse(p.steps || '[]') + return { + ...p, + steps: steps.map(s => ({ ...s, template_name: nameMap.get(s.template_id) || 'Unknown' })), + runs: runMap.get(p.id) || { total: 0, completed: 0, failed: 0, running: 0 }, + } + }) + + return NextResponse.json({ pipelines: parsed }) + } catch (error) { + console.error('GET /api/pipelines error:', error) + return NextResponse.json({ error: 'Failed to fetch pipelines' }, { status: 500 }) + } +} + +/** + * POST /api/pipelines - Create a pipeline + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + const { name, description, steps = [] } = body + + if (!name) { + return NextResponse.json({ error: 'Name is required' }, { status: 400 }) + } + if (!Array.isArray(steps) || steps.length < 2) { + return NextResponse.json({ error: 'Pipeline needs at least 2 steps' }, { status: 400 }) + } + + // Validate template IDs exist + const templateIds = steps.map((s: PipelineStep) => s.template_id) + const existing = db.prepare( + `SELECT id FROM workflow_templates WHERE id IN (${templateIds.map(() => '?').join(',')})` + ).all(...templateIds) as Array<{ id: number }> + if (existing.length !== new Set(templateIds).size) { + return NextResponse.json({ error: 'One or more template IDs not found' }, { status: 400 }) + } + + const cleanSteps = steps.map((s: PipelineStep) => ({ + template_id: s.template_id, + on_failure: s.on_failure || 'stop', + })) + + const result = db.prepare(` + INSERT INTO workflow_pipelines (name, description, steps, created_by) + VALUES (?, ?, ?, ?) + `).run(name, description || null, JSON.stringify(cleanSteps), auth.user?.username || 'system') + + db_helpers.logActivity('pipeline_created', 'pipeline', Number(result.lastInsertRowid), auth.user?.username || 'system', `Created pipeline: ${name}`) + + const pipeline = db.prepare('SELECT * FROM workflow_pipelines WHERE id = ?').get(result.lastInsertRowid) as Pipeline + return NextResponse.json({ pipeline: { ...pipeline, steps: JSON.parse(pipeline.steps) } }, { status: 201 }) + } catch (error) { + console.error('POST /api/pipelines error:', error) + return NextResponse.json({ error: 'Failed to create pipeline' }, { status: 500 }) + } +} + +/** + * PUT /api/pipelines - Update a pipeline + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + const { id, ...updates } = body + + if (!id) return NextResponse.json({ error: 'Pipeline ID required' }, { status: 400 }) + + const existing = db.prepare('SELECT * FROM workflow_pipelines WHERE id = ?').get(id) as Pipeline + if (!existing) return NextResponse.json({ error: 'Pipeline not found' }, { status: 404 }) + + const fields: string[] = [] + const params: any[] = [] + + if (updates.name !== undefined) { fields.push('name = ?'); params.push(updates.name) } + if (updates.description !== undefined) { fields.push('description = ?'); params.push(updates.description) } + if (updates.steps !== undefined) { + fields.push('steps = ?') + params.push(JSON.stringify(updates.steps)) + } + + if (fields.length === 0) { + // Usage tracking + fields.push('use_count = use_count + 1', 'last_used_at = ?') + params.push(Math.floor(Date.now() / 1000)) + } + + fields.push('updated_at = ?') + params.push(Math.floor(Date.now() / 1000)) + params.push(id) + + db.prepare(`UPDATE workflow_pipelines SET ${fields.join(', ')} WHERE id = ?`).run(...params) + + const updated = db.prepare('SELECT * FROM workflow_pipelines WHERE id = ?').get(id) as Pipeline + return NextResponse.json({ pipeline: { ...updated, steps: JSON.parse(updated.steps) } }) + } catch (error) { + console.error('PUT /api/pipelines error:', error) + return NextResponse.json({ error: 'Failed to update pipeline' }, { status: 500 }) + } +} + +/** + * DELETE /api/pipelines - Delete a pipeline + */ +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + const id = searchParams.get('id') + if (!id) return NextResponse.json({ error: 'Pipeline ID required' }, { status: 400 }) + + db.prepare('DELETE FROM workflow_pipelines WHERE id = ?').run(parseInt(id)) + return NextResponse.json({ success: true }) + } catch (error) { + console.error('DELETE /api/pipelines error:', error) + return NextResponse.json({ error: 'Failed to delete pipeline' }, { status: 500 }) + } +} diff --git a/src/app/api/pipelines/run/route.ts b/src/app/api/pipelines/run/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..f0f7cc86359124b706a26f384fcec5c410730a6a --- /dev/null +++ b/src/app/api/pipelines/run/route.ts @@ -0,0 +1,301 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers } from '@/lib/db' +import { requireRole } from '@/lib/auth' +import { eventBus } from '@/lib/event-bus' + +interface PipelineStep { + template_id: number + on_failure: 'stop' | 'continue' +} + +interface RunStepState { + step_index: number + template_id: number + template_name: string + status: 'pending' | 'running' | 'completed' | 'failed' | 'skipped' + spawn_id: string | null + started_at: number | null + completed_at: number | null + error: string | null +} + +interface PipelineRun { + id: number + pipeline_id: number + status: string + current_step: number + steps_snapshot: string + started_at: number | null + completed_at: number | null + triggered_by: string + created_at: number +} + +/** + * GET /api/pipelines/run - Get pipeline runs + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + const pipelineId = searchParams.get('pipeline_id') + const runId = searchParams.get('id') + const limit = parseInt(searchParams.get('limit') || '20') + + if (runId) { + const run = db.prepare('SELECT * FROM pipeline_runs WHERE id = ?').get(parseInt(runId)) as PipelineRun | undefined + if (!run) return NextResponse.json({ error: 'Run not found' }, { status: 404 }) + return NextResponse.json({ run: { ...run, steps_snapshot: JSON.parse(run.steps_snapshot) } }) + } + + let query = 'SELECT * FROM pipeline_runs' + const params: any[] = [] + + if (pipelineId) { + query += ' WHERE pipeline_id = ?' + params.push(parseInt(pipelineId)) + } + + query += ' ORDER BY created_at DESC LIMIT ?' + params.push(limit) + + const runs = db.prepare(query).all(...params) as PipelineRun[] + + // Enrich with pipeline names + const pipelineIds = [...new Set(runs.map(r => r.pipeline_id))] + const pipelines = pipelineIds.length > 0 + ? db.prepare(`SELECT id, name FROM workflow_pipelines WHERE id IN (${pipelineIds.map(() => '?').join(',')})`).all(...pipelineIds) as Array<{ id: number; name: string }> + : [] + const nameMap = new Map(pipelines.map(p => [p.id, p.name])) + + const parsed = runs.map(r => ({ + ...r, + pipeline_name: nameMap.get(r.pipeline_id) || 'Deleted Pipeline', + steps_snapshot: JSON.parse(r.steps_snapshot), + })) + + return NextResponse.json({ runs: parsed }) + } catch (error) { + console.error('GET /api/pipelines/run error:', error) + return NextResponse.json({ error: 'Failed to fetch runs' }, { status: 500 }) + } +} + +/** + * POST /api/pipelines/run - Start a pipeline run or advance a running one + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + const { action, pipeline_id, run_id } = body + + if (action === 'start') { + return startPipeline(db, pipeline_id, auth.user?.username || 'system') + } else if (action === 'advance') { + return advanceRun(db, run_id, body.success ?? true, body.error) + } else if (action === 'cancel') { + return cancelRun(db, run_id) + } + + return NextResponse.json({ error: 'Invalid action. Use: start, advance, cancel' }, { status: 400 }) + } catch (error) { + console.error('POST /api/pipelines/run error:', error) + return NextResponse.json({ error: 'Failed to process pipeline run' }, { status: 500 }) + } +} + +/** Spawn a single pipeline step using `openclaw agent` */ +async function spawnStep( + db: ReturnType, + pipelineName: string, + template: { name: string; model: string; task_prompt: string; timeout_seconds: number }, + steps: RunStepState[], + stepIdx: number, + runId: number +): Promise<{ success: boolean; stdout?: string; error?: string }> { + try { + const { runOpenClaw } = await import('@/lib/command') + const args = [ + 'agent', + '--message', `[Pipeline: ${pipelineName} | Step ${stepIdx + 1}] ${template.task_prompt}`, + '--timeout', String(template.timeout_seconds), + '--json', + ] + const { stdout } = await runOpenClaw(args, { timeoutMs: 15000 }) + + const spawnId = `pipeline-${runId}-step-${stepIdx}-${Date.now()}` + steps[stepIdx].spawn_id = spawnId + db.prepare('UPDATE pipeline_runs SET steps_snapshot = ? WHERE id = ?').run(JSON.stringify(steps), runId) + + return { success: true, stdout: stdout.trim() } + } catch (err: any) { + // Spawn failed - record error but keep pipeline running for manual advance + steps[stepIdx].error = err.message + db.prepare('UPDATE pipeline_runs SET steps_snapshot = ? WHERE id = ?').run(JSON.stringify(steps), runId) + + return { success: false, error: err.message } + } +} + +async function startPipeline(db: ReturnType, pipelineId: number, triggeredBy: string) { + const pipeline = db.prepare('SELECT * FROM workflow_pipelines WHERE id = ?').get(pipelineId) as any + if (!pipeline) return NextResponse.json({ error: 'Pipeline not found' }, { status: 404 }) + + const steps: PipelineStep[] = JSON.parse(pipeline.steps || '[]') + if (steps.length === 0) return NextResponse.json({ error: 'Pipeline has no steps' }, { status: 400 }) + + // Get template names for snapshot + const templateIds = steps.map(s => s.template_id) + const templates = db.prepare( + `SELECT id, name, model, task_prompt, timeout_seconds FROM workflow_templates WHERE id IN (${templateIds.map(() => '?').join(',')})` + ).all(...templateIds) as Array<{ id: number; name: string; model: string; task_prompt: string; timeout_seconds: number }> + const templateMap = new Map(templates.map(t => [t.id, t])) + + // Build step snapshot + const stepsSnapshot: RunStepState[] = steps.map((s, i) => ({ + step_index: i, + template_id: s.template_id, + template_name: templateMap.get(s.template_id)?.name || 'Unknown', + on_failure: s.on_failure, + status: i === 0 ? 'running' : 'pending', + spawn_id: null, + started_at: i === 0 ? Math.floor(Date.now() / 1000) : null, + completed_at: null, + error: null, + })) + + const now = Math.floor(Date.now() / 1000) + const result = db.prepare(` + INSERT INTO pipeline_runs (pipeline_id, status, current_step, steps_snapshot, started_at, triggered_by) + VALUES (?, 'running', 0, ?, ?, ?) + `).run(pipelineId, JSON.stringify(stepsSnapshot), now, triggeredBy) + + const runId = Number(result.lastInsertRowid) + + // Update pipeline usage + db.prepare(` + UPDATE workflow_pipelines SET use_count = use_count + 1, last_used_at = ?, updated_at = ? WHERE id = ? + `).run(now, now, pipelineId) + + // Spawn first step + const firstTemplate = templateMap.get(steps[0].template_id) + let spawnResult: any = null + if (firstTemplate) { + spawnResult = await spawnStep(db, pipeline.name, firstTemplate, stepsSnapshot, 0, runId) + } + + db_helpers.logActivity('pipeline_started', 'pipeline', pipelineId, triggeredBy, `Started pipeline: ${pipeline.name}`, { run_id: runId }) + + eventBus.broadcast('activity.created', { + type: 'pipeline_started', + entity_type: 'pipeline', + entity_id: pipelineId, + description: `Pipeline "${pipeline.name}" started`, + data: { run_id: runId }, + }) + + return NextResponse.json({ + run: { + id: runId, + pipeline_id: pipelineId, + status: stepsSnapshot[0].status === 'failed' ? 'failed' : 'running', + current_step: 0, + steps_snapshot: stepsSnapshot, + spawn: spawnResult, + } + }, { status: 201 }) +} + +async function advanceRun(db: ReturnType, runId: number, success: boolean, errorMsg?: string) { + if (!runId) return NextResponse.json({ error: 'run_id required' }, { status: 400 }) + + const run = db.prepare('SELECT * FROM pipeline_runs WHERE id = ?').get(runId) as PipelineRun | undefined + if (!run) return NextResponse.json({ error: 'Run not found' }, { status: 404 }) + if (run.status !== 'running') return NextResponse.json({ error: `Run is ${run.status}, not running` }, { status: 400 }) + + const steps: (RunStepState & { on_failure?: string })[] = JSON.parse(run.steps_snapshot) + const currentIdx = run.current_step + const now = Math.floor(Date.now() / 1000) + + // Mark current step as completed/failed + steps[currentIdx].status = success ? 'completed' : 'failed' + steps[currentIdx].completed_at = now + if (errorMsg) steps[currentIdx].error = errorMsg + + // Determine next action + const nextIdx = currentIdx + 1 + const onFailure = steps[currentIdx].on_failure || 'stop' + + if (!success && onFailure === 'stop') { + // Mark remaining steps as skipped + for (let i = nextIdx; i < steps.length; i++) steps[i].status = 'skipped' + db.prepare('UPDATE pipeline_runs SET status = ?, current_step = ?, steps_snapshot = ?, completed_at = ? WHERE id = ?') + .run('failed', currentIdx, JSON.stringify(steps), now, runId) + return NextResponse.json({ run: { id: runId, status: 'failed', steps_snapshot: steps } }) + } + + if (nextIdx >= steps.length) { + // Pipeline complete + const finalStatus = steps.some(s => s.status === 'failed') ? 'completed' : 'completed' + db.prepare('UPDATE pipeline_runs SET status = ?, current_step = ?, steps_snapshot = ?, completed_at = ? WHERE id = ?') + .run(finalStatus, currentIdx, JSON.stringify(steps), now, runId) + + eventBus.broadcast('activity.created', { + type: 'pipeline_completed', + entity_type: 'pipeline', + entity_id: run.pipeline_id, + description: `Pipeline run #${runId} completed`, + }) + + return NextResponse.json({ run: { id: runId, status: finalStatus, steps_snapshot: steps } }) + } + + // Spawn next step + steps[nextIdx].status = 'running' + steps[nextIdx].started_at = now + + const template = db.prepare('SELECT id, name, model, task_prompt, timeout_seconds FROM workflow_templates WHERE id = ?') + .get(steps[nextIdx].template_id) as any + + let spawnResult: any = null + if (template) { + const pipeline = db.prepare('SELECT name FROM workflow_pipelines WHERE id = ?').get(run.pipeline_id) as any + spawnResult = await spawnStep(db, pipeline?.name || '?', template, steps, nextIdx, runId) + } + + db.prepare('UPDATE pipeline_runs SET current_step = ?, steps_snapshot = ? WHERE id = ?') + .run(nextIdx, JSON.stringify(steps), runId) + + return NextResponse.json({ + run: { id: runId, status: 'running', current_step: nextIdx, steps_snapshot: steps, spawn: spawnResult } + }) +} + +function cancelRun(db: ReturnType, runId: number) { + if (!runId) return NextResponse.json({ error: 'run_id required' }, { status: 400 }) + + const run = db.prepare('SELECT * FROM pipeline_runs WHERE id = ?').get(runId) as PipelineRun | undefined + if (!run) return NextResponse.json({ error: 'Run not found' }, { status: 404 }) + if (run.status !== 'running' && run.status !== 'pending') { + return NextResponse.json({ error: `Run is ${run.status}, cannot cancel` }, { status: 400 }) + } + + const steps: RunStepState[] = JSON.parse(run.steps_snapshot) + const now = Math.floor(Date.now() / 1000) + + for (const step of steps) { + if (step.status === 'pending' || step.status === 'running') { + step.status = 'skipped' + step.completed_at = now + } + } + + db.prepare('UPDATE pipeline_runs SET status = ?, steps_snapshot = ?, completed_at = ? WHERE id = ?') + .run('cancelled', JSON.stringify(steps), now, runId) + + return NextResponse.json({ run: { id: runId, status: 'cancelled', steps_snapshot: steps } }) +} diff --git a/src/app/api/quality-review/route.ts b/src/app/api/quality-review/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..1d18801ce4c141ea826aa4b12f4ba1134dffdd15 --- /dev/null +++ b/src/app/api/quality-review/route.ts @@ -0,0 +1,109 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers } from '@/lib/db' +import { requireRole } from '@/lib/auth' + +export async function GET(request: NextRequest) { + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + const taskIdsParam = searchParams.get('taskIds') + const taskId = parseInt(searchParams.get('taskId') || '') + + if (taskIdsParam) { + const ids = taskIdsParam + .split(',') + .map((id) => parseInt(id.trim())) + .filter((id) => !Number.isNaN(id)) + + if (ids.length === 0) { + return NextResponse.json({ error: 'taskIds must include at least one numeric id' }, { status: 400 }) + } + + const placeholders = ids.map(() => '?').join(',') + const rows = db.prepare(` + SELECT * FROM quality_reviews + WHERE task_id IN (${placeholders}) + ORDER BY task_id ASC, created_at DESC + `).all(...ids) as Array<{ task_id: number; reviewer?: string; status?: string; created_at?: number }> + + const byTask: Record = {} + for (const id of ids) { + byTask[id] = null + } + + for (const row of rows) { + const existing = byTask[row.task_id] + if (!existing || (row.created_at || 0) > (existing.created_at || 0)) { + byTask[row.task_id] = { status: row.status, reviewer: row.reviewer, created_at: row.created_at } + } + } + + return NextResponse.json({ latest: byTask }) + } + + if (isNaN(taskId)) { + return NextResponse.json({ error: 'taskId is required' }, { status: 400 }) + } + + const reviews = db.prepare(` + SELECT * FROM quality_reviews + WHERE task_id = ? + ORDER BY created_at DESC + LIMIT 10 + `).all(taskId) + + return NextResponse.json({ reviews }) + } catch (error) { + console.error('GET /api/quality-review error:', error) + return NextResponse.json({ error: 'Failed to fetch quality reviews' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + const taskId = parseInt(body.taskId) + const reviewer = (body.reviewer || 'aegis').trim() + const status = (body.status || '').trim() + const notes = body.notes ? String(body.notes) : null + + if (isNaN(taskId) || !reviewer || !status) { + return NextResponse.json({ error: 'taskId, reviewer, and status are required' }, { status: 400 }) + } + + if (!['approved', 'rejected'].includes(status)) { + return NextResponse.json({ error: 'status must be approved or rejected' }, { status: 400 }) + } + if (!notes || !String(notes).trim()) { + return NextResponse.json({ error: 'notes are required for quality reviews' }, { status: 400 }) + } + + const task = db.prepare('SELECT id, title FROM tasks WHERE id = ?').get(taskId) as any + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }) + } + + const result = db.prepare(` + INSERT INTO quality_reviews (task_id, reviewer, status, notes) + VALUES (?, ?, ?, ?) + `).run(taskId, reviewer, status, notes) + + db_helpers.logActivity( + 'quality_review', + 'task', + taskId, + reviewer, + `Quality review ${status} for task: ${task.title}`, + { status, notes } + ) + + return NextResponse.json({ success: true, id: result.lastInsertRowid }) + } catch (error) { + console.error('POST /api/quality-review error:', error) + return NextResponse.json({ error: 'Failed to create quality review' }, { status: 500 }) + } +} diff --git a/src/app/api/scheduler/route.ts b/src/app/api/scheduler/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..ae8221348d714eb3ee3a8a67f4993aafd88be2ba --- /dev/null +++ b/src/app/api/scheduler/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getSchedulerStatus, triggerTask } from '@/lib/scheduler' + +/** + * GET /api/scheduler - Get scheduler status + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + return NextResponse.json({ tasks: getSchedulerStatus() }) +} + +/** + * POST /api/scheduler - Manually trigger a scheduled task + * Body: { task_id: 'auto_backup' | 'auto_cleanup' | 'agent_heartbeat' } + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const body = await request.json().catch(() => ({})) + const taskId = body.task_id + + if (!taskId || !['auto_backup', 'auto_cleanup', 'agent_heartbeat'].includes(taskId)) { + return NextResponse.json({ error: 'task_id required: auto_backup, auto_cleanup, or agent_heartbeat' }, { status: 400 }) + } + + const result = await triggerTask(taskId) + return NextResponse.json(result, { status: result.ok ? 200 : 500 }) +} diff --git a/src/app/api/search/route.ts b/src/app/api/search/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..6ce6aba1bea6ed08eb987f64c3429aac58c3147a --- /dev/null +++ b/src/app/api/search/route.ts @@ -0,0 +1,206 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase } from '@/lib/db' + +interface SearchResult { + type: 'task' | 'agent' | 'activity' | 'audit' | 'message' | 'notification' | 'webhook' | 'pipeline' + id: number + title: string + subtitle?: string + excerpt?: string + created_at: number + relevance: number +} + +/** + * GET /api/search?q=&type=&limit= + * Global search across all MC entities. + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'viewer') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const { searchParams } = new URL(request.url) + const query = searchParams.get('q')?.trim() + const typeFilter = searchParams.get('type') + const limit = Math.min(parseInt(searchParams.get('limit') || '30'), 100) + + if (!query || query.length < 2) { + return NextResponse.json({ error: 'Query must be at least 2 characters' }, { status: 400 }) + } + + const db = getDatabase() + const likeQ = `%${query}%` + const results: SearchResult[] = [] + + // Search tasks + if (!typeFilter || typeFilter === 'task') { + try { + const tasks = db.prepare(` + SELECT id, title, description, status, assigned_to, created_at + FROM tasks WHERE title LIKE ? OR description LIKE ? OR assigned_to LIKE ? + ORDER BY created_at DESC LIMIT ? + `).all(likeQ, likeQ, likeQ, limit) as any[] + for (const t of tasks) { + results.push({ + type: 'task', + id: t.id, + title: t.title, + subtitle: `${t.status} ${t.assigned_to ? `· ${t.assigned_to}` : ''}`, + excerpt: truncateMatch(t.description, query), + created_at: t.created_at, + relevance: t.title.toLowerCase().includes(query.toLowerCase()) ? 2 : 1, + }) + } + } catch { /* table might not exist */ } + } + + // Search agents + if (!typeFilter || typeFilter === 'agent') { + try { + const agents = db.prepare(` + SELECT id, name, role, status, last_activity, created_at + FROM agents WHERE name LIKE ? OR role LIKE ? OR last_activity LIKE ? + ORDER BY created_at DESC LIMIT ? + `).all(likeQ, likeQ, likeQ, limit) as any[] + for (const a of agents) { + results.push({ + type: 'agent', + id: a.id, + title: a.name, + subtitle: `${a.role} · ${a.status}`, + excerpt: a.last_activity, + created_at: a.created_at, + relevance: a.name.toLowerCase().includes(query.toLowerCase()) ? 2 : 1, + }) + } + } catch { /* table might not exist */ } + } + + // Search activities + if (!typeFilter || typeFilter === 'activity') { + try { + const activities = db.prepare(` + SELECT id, type, actor, description, created_at + FROM activities WHERE description LIKE ? OR actor LIKE ? + ORDER BY created_at DESC LIMIT ? + `).all(likeQ, likeQ, limit) as any[] + for (const a of activities) { + results.push({ + type: 'activity', + id: a.id, + title: a.description, + subtitle: `by ${a.actor}`, + created_at: a.created_at, + relevance: 1, + }) + } + } catch { /* table might not exist */ } + } + + // Search audit log + if (!typeFilter || typeFilter === 'audit') { + try { + const audits = db.prepare(` + SELECT id, action, actor, detail, created_at + FROM audit_log WHERE action LIKE ? OR actor LIKE ? OR detail LIKE ? + ORDER BY created_at DESC LIMIT ? + `).all(likeQ, likeQ, likeQ, limit) as any[] + for (const a of audits) { + results.push({ + type: 'audit', + id: a.id, + title: a.action, + subtitle: `by ${a.actor}`, + excerpt: truncateMatch(a.detail, query), + created_at: a.created_at, + relevance: 1, + }) + } + } catch { /* table might not exist */ } + } + + // Search messages + if (!typeFilter || typeFilter === 'message') { + try { + const messages = db.prepare(` + SELECT id, from_agent, to_agent, content, conversation_id, created_at + FROM messages WHERE content LIKE ? OR from_agent LIKE ? + ORDER BY created_at DESC LIMIT ? + `).all(likeQ, likeQ, limit) as any[] + for (const m of messages) { + results.push({ + type: 'message', + id: m.id, + title: `${m.from_agent} → ${m.to_agent || 'all'}`, + subtitle: m.conversation_id, + excerpt: truncateMatch(m.content, query), + created_at: m.created_at, + relevance: 1, + }) + } + } catch { /* table might not exist */ } + } + + // Search webhooks + if (!typeFilter || typeFilter === 'webhook') { + try { + const webhooks = db.prepare(` + SELECT id, name, url, events, created_at + FROM webhooks WHERE name LIKE ? OR url LIKE ? + ORDER BY created_at DESC LIMIT ? + `).all(likeQ, likeQ, limit) as any[] + for (const w of webhooks) { + results.push({ + type: 'webhook', + id: w.id, + title: w.name, + subtitle: w.url, + created_at: w.created_at, + relevance: w.name.toLowerCase().includes(query.toLowerCase()) ? 2 : 1, + }) + } + } catch { /* table might not exist */ } + } + + // Search pipelines + if (!typeFilter || typeFilter === 'pipeline') { + try { + const pipelines = db.prepare(` + SELECT id, name, description, created_at + FROM workflow_pipelines WHERE name LIKE ? OR description LIKE ? + ORDER BY created_at DESC LIMIT ? + `).all(likeQ, likeQ, limit) as any[] + for (const p of pipelines) { + results.push({ + type: 'pipeline', + id: p.id, + title: p.name, + excerpt: truncateMatch(p.description, query), + created_at: p.created_at, + relevance: p.name.toLowerCase().includes(query.toLowerCase()) ? 2 : 1, + }) + } + } catch { /* table might not exist */ } + } + + // Sort by relevance then recency + results.sort((a, b) => b.relevance - a.relevance || b.created_at - a.created_at) + + return NextResponse.json({ + query, + count: results.length, + results: results.slice(0, limit), + }) +} + +function truncateMatch(text: string | null, query: string, maxLen = 120): string | undefined { + if (!text) return undefined + const lower = text.toLowerCase() + const idx = lower.indexOf(query.toLowerCase()) + if (idx === -1) return text.slice(0, maxLen) + (text.length > maxLen ? '...' : '') + const start = Math.max(0, idx - 40) + const end = Math.min(text.length, idx + query.length + 80) + const excerpt = (start > 0 ? '...' : '') + text.slice(start, end) + (end < text.length ? '...' : '') + return excerpt +} diff --git a/src/app/api/sessions/route.ts b/src/app/api/sessions/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..05414d470e3b4feefc8a9f309b252de80579e52c --- /dev/null +++ b/src/app/api/sessions/route.ts @@ -0,0 +1,52 @@ +import { NextResponse } from 'next/server' +import { getAllGatewaySessions } from '@/lib/sessions' + +export async function GET() { + try { + const gatewaySessions = getAllGatewaySessions() + + const sessions = gatewaySessions.map((s) => { + const total = s.totalTokens || 0 + const context = s.contextTokens || 35000 + const pct = context > 0 ? Math.round((total / context) * 100) : 0 + return { + id: s.sessionId || s.key, + key: s.key, + agent: s.agent, + kind: s.chatType || 'unknown', + age: formatAge(s.updatedAt), + model: s.model, + tokens: `${formatTokens(total)}/${formatTokens(context)} (${pct}%)`, + channel: s.channel, + flags: [], + active: s.active, + startTime: s.updatedAt, + lastActivity: s.updatedAt, + } + }) + + return NextResponse.json({ sessions }) + } catch (error) { + console.error('Sessions API error:', error) + return NextResponse.json({ sessions: [] }) + } +} + +function formatTokens(n: number): string { + if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}m` + if (n >= 1000) return `${Math.round(n / 1000)}k` + return String(n) +} + +function formatAge(timestamp: number): string { + if (!timestamp) return '-' + const diff = Date.now() - timestamp + const mins = Math.floor(diff / 60000) + const hours = Math.floor(mins / 60) + const days = Math.floor(hours / 24) + if (days > 0) return `${days}d` + if (hours > 0) return `${hours}h` + return `${mins}m` +} + +export const dynamic = 'force-dynamic' diff --git a/src/app/api/settings/route.ts b/src/app/api/settings/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf4c71180f1a7a8a51690ff5b72d3cb06874d975 --- /dev/null +++ b/src/app/api/settings/route.ts @@ -0,0 +1,186 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase, logAuditEvent } from '@/lib/db' +import { config } from '@/lib/config' + +interface SettingRow { + key: string + value: string + description: string | null + category: string + updated_by: string | null + updated_at: number +} + +// Default settings definitions (category, description, default value) +const settingDefinitions: Record = { + // Retention + 'retention.activities_days': { category: 'retention', description: 'Days to keep activity records', default: String(config.retention.activities) }, + 'retention.audit_log_days': { category: 'retention', description: 'Days to keep audit log entries', default: String(config.retention.auditLog) }, + 'retention.logs_days': { category: 'retention', description: 'Days to keep log files', default: String(config.retention.logs) }, + 'retention.notifications_days': { category: 'retention', description: 'Days to keep notifications', default: String(config.retention.notifications) }, + 'retention.pipeline_runs_days': { category: 'retention', description: 'Days to keep pipeline run history', default: String(config.retention.pipelineRuns) }, + 'retention.token_usage_days': { category: 'retention', description: 'Days to keep token usage data', default: String(config.retention.tokenUsage) }, + + // Gateway + 'gateway.host': { category: 'gateway', description: 'Gateway hostname', default: config.gatewayHost }, + 'gateway.port': { category: 'gateway', description: 'Gateway port number', default: String(config.gatewayPort) }, + + // General + 'general.site_name': { category: 'general', description: 'Mission Control display name', default: 'Mission Control' }, + 'general.auto_cleanup': { category: 'general', description: 'Enable automatic data cleanup', default: 'false' }, + 'general.auto_backup': { category: 'general', description: 'Enable automatic daily backups', default: 'false' }, + 'general.backup_retention_count': { category: 'general', description: 'Number of backup files to keep', default: '10' }, +} + +/** + * GET /api/settings - List all settings (grouped by category) + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const db = getDatabase() + const rows = db.prepare('SELECT * FROM settings ORDER BY category, key').all() as SettingRow[] + const stored = new Map(rows.map(r => [r.key, r])) + + // Merge defaults with stored values + const settings: Array<{ + key: string + value: string + description: string + category: string + updated_by: string | null + updated_at: number | null + is_default: boolean + }> = [] + + for (const [key, def] of Object.entries(settingDefinitions)) { + const row = stored.get(key) + settings.push({ + key, + value: row?.value ?? def.default, + description: row?.description ?? def.description, + category: row?.category ?? def.category, + updated_by: row?.updated_by ?? null, + updated_at: row?.updated_at ?? null, + is_default: !row, + }) + } + + // Also include any custom settings not in definitions + for (const row of rows) { + if (!settingDefinitions[row.key]) { + settings.push({ + key: row.key, + value: row.value, + description: row.description ?? '', + category: row.category, + updated_by: row.updated_by, + updated_at: row.updated_at, + is_default: false, + }) + } + } + + // Group by category + const grouped: Record = {} + for (const s of settings) { + if (!grouped[s.category]) grouped[s.category] = [] + grouped[s.category].push(s) + } + + return NextResponse.json({ settings, grouped }) +} + +/** + * PUT /api/settings - Update one or more settings + * Body: { settings: { key: value, ... } } + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const body = await request.json().catch(() => null) + if (!body?.settings || typeof body.settings !== 'object') { + return NextResponse.json({ error: 'settings object required' }, { status: 400 }) + } + + const db = getDatabase() + const upsert = db.prepare(` + INSERT INTO settings (key, value, description, category, updated_by, updated_at) + VALUES (?, ?, ?, ?, ?, unixepoch()) + ON CONFLICT(key) DO UPDATE SET + value = excluded.value, + updated_by = excluded.updated_by, + updated_at = unixepoch() + `) + + const updated: string[] = [] + const changes: Record = {} + + const txn = db.transaction(() => { + for (const [key, value] of Object.entries(body.settings)) { + const strValue = String(value) + const def = settingDefinitions[key] + const category = def?.category ?? 'custom' + const description = def?.description ?? null + + // Get old value for audit + const existing = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined + changes[key] = { old: existing?.value ?? null, new: strValue } + + upsert.run(key, strValue, description, category, auth.user.username) + updated.push(key) + } + }) + + txn() + + // Audit log + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'settings_update', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { updated_keys: updated, changes }, + ip_address: ipAddress, + }) + + return NextResponse.json({ updated, count: updated.length }) +} + +/** + * DELETE /api/settings?key=... - Reset a setting to default + */ +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const { searchParams } = new URL(request.url) + const key = searchParams.get('key') + + if (!key) { + return NextResponse.json({ error: 'key parameter required' }, { status: 400 }) + } + + const db = getDatabase() + const existing = db.prepare('SELECT value FROM settings WHERE key = ?').get(key) as { value: string } | undefined + + if (!existing) { + return NextResponse.json({ error: 'Setting not found or already at default' }, { status: 404 }) + } + + db.prepare('DELETE FROM settings WHERE key = ?').run(key) + + const ipAddress = request.headers.get('x-forwarded-for') || request.headers.get('x-real-ip') || 'unknown' + logAuditEvent({ + action: 'settings_reset', + actor: auth.user.username, + actor_id: auth.user.id, + detail: { key, old_value: existing.value }, + ip_address: ipAddress, + }) + + return NextResponse.json({ reset: key, default_value: settingDefinitions[key]?.default ?? null }) +} diff --git a/src/app/api/spawn/route.ts b/src/app/api/spawn/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..d32b9c6bd59cf5b29effb3816b6861b3c16a98fc --- /dev/null +++ b/src/app/api/spawn/route.ts @@ -0,0 +1,179 @@ +import { NextRequest, NextResponse } from 'next/server' +import { runClawdbot } from '@/lib/command' +import { requireRole } from '@/lib/auth' +import { config } from '@/lib/config' +import { readdir, readFile, stat } from 'fs/promises' +import { join } from 'path' + +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const { task, model, label, timeoutSeconds } = await request.json() + + // Validate required fields + if (!task || !model || !label) { + return NextResponse.json( + { error: 'Missing required fields: task, model, label' }, + { status: 400 } + ) + } + + // Validate timeout + const timeout = parseInt(timeoutSeconds) || 300 + if (timeout < 10 || timeout > 3600) { + return NextResponse.json( + { error: 'Timeout must be between 10 and 3600 seconds' }, + { status: 400 } + ) + } + + // Generate spawn ID + const spawnId = `spawn-${Date.now()}-${Math.random().toString(36).substr(2, 9)}` + + // Construct the spawn command + // Using OpenClaw's sessions_spawn function via clawdbot CLI + const spawnPayload = { + task, + model, + label, + runTimeoutSeconds: timeout + } + const commandArg = `sessions_spawn(${JSON.stringify(spawnPayload)})` + + try { + // Execute the spawn command + const { stdout, stderr } = await runClawdbot(['-c', commandArg], { + timeoutMs: 10000 + }) + + // Parse the response to extract session info + let sessionInfo = null + try { + // Look for session information in stdout + const sessionMatch = stdout.match(/Session created: (.+)/) + if (sessionMatch) { + sessionInfo = sessionMatch[1] + } + } catch (parseError) { + console.error('Failed to parse session info:', parseError) + } + + return NextResponse.json({ + success: true, + spawnId, + sessionInfo, + task, + model, + label, + timeoutSeconds: timeout, + createdAt: Date.now(), + stdout: stdout.trim(), + stderr: stderr.trim() + }) + + } catch (execError: any) { + console.error('Spawn execution error:', execError) + + return NextResponse.json({ + success: false, + spawnId, + error: execError.message || 'Failed to spawn agent', + task, + model, + label, + timeoutSeconds: timeout, + createdAt: Date.now() + }, { status: 500 }) + } + + } catch (error) { + console.error('Spawn API error:', error) + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ) + } +} + +// Get spawn history +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const limit = parseInt(searchParams.get('limit') || '50') + + // In a real implementation, you'd store spawn history in a database + // For now, we'll try to read recent spawn activity from logs + + try { + if (!config.logsDir) { + return NextResponse.json({ history: [] }) + } + + const files = await readdir(config.logsDir) + const logFiles = await Promise.all( + files + .filter((file) => file.endsWith('.log')) + .map(async (file) => { + const fullPath = join(config.logsDir, file) + const stats = await stat(fullPath) + return { file, fullPath, mtime: stats.mtime.getTime() } + }) + ) + + const recentLogs = logFiles + .sort((a, b) => b.mtime - a.mtime) + .slice(0, 5) + + const lines: string[] = [] + + for (const log of recentLogs) { + const content = await readFile(log.fullPath, 'utf-8') + const matched = content + .split('\n') + .filter((line) => line.includes('sessions_spawn')) + lines.push(...matched) + } + + const spawnHistory = lines + .slice(-limit) + .map((line, index) => { + try { + const timestampMatch = line.match( + /(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/ + ) + const modelMatch = line.match(/model[:\s]+"([^"]+)"/) + const taskMatch = line.match(/task[:\s]+"([^"]+)"/) + + return { + id: `history-${Date.now()}-${index}`, + timestamp: timestampMatch + ? new Date(timestampMatch[1]).getTime() + : Date.now(), + model: modelMatch ? modelMatch[1] : 'unknown', + task: taskMatch ? taskMatch[1] : 'unknown', + status: 'completed', + line: line.trim() + } + } catch (parseError) { + return null + } + }) + .filter(Boolean) + + return NextResponse.json({ history: spawnHistory }) + + } catch (logError) { + // If we can't read logs, return empty history + return NextResponse.json({ history: [] }) + } + + } catch (error) { + console.error('Spawn history API error:', error) + return NextResponse.json( + { error: 'Internal server error' }, + { status: 500 } + ) + } +} diff --git a/src/app/api/standup/route.ts b/src/app/api/standup/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..2bdf2368716e9c05ff402bf0f0183d29c5a8b745 --- /dev/null +++ b/src/app/api/standup/route.ts @@ -0,0 +1,247 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, db_helpers } from '@/lib/db'; +import { requireRole } from '@/lib/auth'; + +/** + * POST /api/standup/generate - Generate daily standup report + * Body: { date?: string, agents?: string[] } + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const body = await request.json(); + + // Parse parameters + const targetDate = body.date || new Date().toISOString().split('T')[0]; // YYYY-MM-DD format + const specificAgents = body.agents; // Optional filter for specific agents + + // Calculate time range for "today" (start and end of the target date) + const startOfDay = Math.floor(new Date(`${targetDate}T00:00:00Z`).getTime() / 1000); + const endOfDay = Math.floor(new Date(`${targetDate}T23:59:59Z`).getTime() / 1000); + + // Get all active agents or filter by specific agents + let agentQuery = 'SELECT * FROM agents'; + const agentParams: any[] = []; + + if (specificAgents && Array.isArray(specificAgents) && specificAgents.length > 0) { + const placeholders = specificAgents.map(() => '?').join(','); + agentQuery += ` WHERE name IN (${placeholders})`; + agentParams.push(...specificAgents); + } + + agentQuery += ' ORDER BY name'; + + const agents = db.prepare(agentQuery).all(...agentParams) as any[]; + + // Generate standup data for each agent + const standupData = agents.map(agent => { + // Completed tasks today + const completedTasks = db.prepare(` + SELECT id, title, status, updated_at + FROM tasks + WHERE assigned_to = ? + AND status = 'done' + AND updated_at BETWEEN ? AND ? + ORDER BY updated_at DESC + `).all(agent.name, startOfDay, endOfDay); + + // Currently in progress tasks + const inProgressTasks = db.prepare(` + SELECT id, title, status, created_at, due_date + FROM tasks + WHERE assigned_to = ? + AND status = 'in_progress' + ORDER BY created_at ASC + `).all(agent.name); + + // Assigned but not started tasks + const assignedTasks = db.prepare(` + SELECT id, title, status, created_at, due_date, priority + FROM tasks + WHERE assigned_to = ? + AND status = 'assigned' + ORDER BY priority DESC, created_at ASC + `).all(agent.name); + + // Review tasks + const reviewTasks = db.prepare(` + SELECT id, title, status, updated_at + FROM tasks + WHERE assigned_to = ? + AND status IN ('review', 'quality_review') + ORDER BY updated_at ASC + `).all(agent.name); + + // Blocked/high priority tasks + const blockedTasks = db.prepare(` + SELECT id, title, status, priority, created_at, metadata + FROM tasks + WHERE assigned_to = ? + AND (priority = 'urgent' OR metadata LIKE '%blocked%') + AND status NOT IN ('done') + ORDER BY priority DESC, created_at ASC + `).all(agent.name); + + // Recent activity count + const activityCount = db.prepare(` + SELECT COUNT(*) as count + FROM activities + WHERE actor = ? + AND created_at BETWEEN ? AND ? + `).get(agent.name, startOfDay, endOfDay) as { count: number }; + + // Comments made today + const commentsToday = db.prepare(` + SELECT COUNT(*) as count + FROM comments + WHERE author = ? + AND created_at BETWEEN ? AND ? + `).get(agent.name, startOfDay, endOfDay) as { count: number }; + + return { + agent: { + name: agent.name, + role: agent.role, + status: agent.status, + last_seen: agent.last_seen, + last_activity: agent.last_activity + }, + completedToday: completedTasks, + inProgress: inProgressTasks, + assigned: assignedTasks, + review: reviewTasks, + blocked: blockedTasks, + activity: { + actionCount: activityCount.count, + commentsCount: commentsToday.count + } + }; + }); + + // Generate summary statistics + const totalCompleted = standupData.reduce((sum, agent) => sum + agent.completedToday.length, 0); + const totalInProgress = standupData.reduce((sum, agent) => sum + agent.inProgress.length, 0); + const totalAssigned = standupData.reduce((sum, agent) => sum + agent.assigned.length, 0); + const totalReview = standupData.reduce((sum, agent) => sum + agent.review.length, 0); + const totalBlocked = standupData.reduce((sum, agent) => sum + agent.blocked.length, 0); + const totalActivity = standupData.reduce((sum, agent) => sum + agent.activity.actionCount, 0); + + // Identify team accomplishments and blockers + const teamAccomplishments = standupData + .flatMap(agent => agent.completedToday.map(task => ({ ...task as any, agent: agent.agent.name }))) + .sort((a: any, b: any) => b.updated_at - a.updated_at); + + const teamBlockers = standupData + .flatMap(agent => agent.blocked.map(task => ({ ...task as any, agent: agent.agent.name }))) + .sort((a: any, b: any) => { + // Sort by priority then by creation date + const priorityOrder: Record = { urgent: 4, high: 3, medium: 2, low: 1 }; + return (priorityOrder[b.priority] || 0) - (priorityOrder[a.priority] || 0) || a.created_at - b.created_at; + }); + + // Get overdue tasks across all agents + const now = Math.floor(Date.now() / 1000); + const overdueTasks = db.prepare(` + SELECT t.*, a.name as agent_name + FROM tasks t + LEFT JOIN agents a ON t.assigned_to = a.name + WHERE t.due_date < ? + AND t.status NOT IN ('done') + ORDER BY t.due_date ASC + `).all(now); + + const standupReport = { + date: targetDate, + generatedAt: new Date().toISOString(), + summary: { + totalAgents: agents.length, + totalCompleted, + totalInProgress, + totalAssigned, + totalReview, + totalBlocked, + totalActivity, + overdue: overdueTasks.length + }, + agentReports: standupData, + teamAccomplishments: teamAccomplishments.slice(0, 10), // Top 10 recent completions + teamBlockers, + overdueTasks + }; + + // Persist standup report + const createdAt = Math.floor(Date.now() / 1000); + db.prepare(` + INSERT OR REPLACE INTO standup_reports (date, report, created_at) + VALUES (?, ?, ?) + `).run(targetDate, JSON.stringify(standupReport), createdAt); + + // Log the standup generation + db_helpers.logActivity( + 'standup_generated', + 'standup', + 0, // No specific entity + 'system', + `Generated daily standup for ${targetDate}`, + { + date: targetDate, + agentCount: agents.length, + tasksSummary: { + completed: totalCompleted, + inProgress: totalInProgress, + assigned: totalAssigned, + review: totalReview, + blocked: totalBlocked + } + } + ); + + return NextResponse.json({ standup: standupReport }); + } catch (error) { + console.error('POST /api/standup/generate error:', error); + return NextResponse.json({ error: 'Failed to generate standup' }, { status: 500 }); + } +} + +/** + * GET /api/standup/history - Get previous standup reports + * Query params: limit, offset + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + + const limit = parseInt(searchParams.get('limit') || '10'); + const offset = parseInt(searchParams.get('offset') || '0'); + + const standupRows = db.prepare(` + SELECT date, report, created_at + FROM standup_reports + ORDER BY created_at DESC + LIMIT ? OFFSET ? + `).all(limit, offset) as Array<{ date: string; report: string; created_at: number }>; + + const standupHistory = standupRows.map((row, index) => { + const report = row.report ? JSON.parse(row.report) : {}; + return { + id: `${row.date}-${index}`, + date: row.date || report.date || 'Unknown', + generatedAt: report.generatedAt || new Date(row.created_at * 1000).toISOString(), + summary: report.summary || {}, + agentCount: report.summary?.totalAgents || 0 + }; + }); + + return NextResponse.json({ + history: standupHistory, + total: standupHistory.length + }); + } catch (error) { + console.error('GET /api/standup/history error:', error); + return NextResponse.json({ error: 'Failed to fetch standup history' }, { status: 500 }); + } +} diff --git a/src/app/api/status/route.ts b/src/app/api/status/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..dba40cac3be1d684f08f1255b3bcd52190059913 --- /dev/null +++ b/src/app/api/status/route.ts @@ -0,0 +1,495 @@ +import { NextRequest, NextResponse } from 'next/server' +import net from 'node:net' +import { statSync } from 'node:fs' +import { runCommand, runOpenClaw, runClawdbot } from '@/lib/command' +import { config } from '@/lib/config' +import { getDatabase } from '@/lib/db' +import { getAllGatewaySessions, getAgentLiveStatuses } from '@/lib/sessions' + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') || 'overview' + + if (action === 'overview') { + const status = await getSystemStatus() + return NextResponse.json(status) + } + + if (action === 'dashboard') { + const data = await getDashboardData() + return NextResponse.json(data) + } + + if (action === 'gateway') { + const gatewayStatus = await getGatewayStatus() + return NextResponse.json(gatewayStatus) + } + + if (action === 'models') { + const models = await getAvailableModels() + return NextResponse.json({ models }) + } + + if (action === 'health') { + const health = await performHealthCheck() + return NextResponse.json(health) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Status API error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} + +/** + * Aggregate all dashboard data in a single request. + * Combines system health, DB stats, audit summary, and recent activity. + */ +async function getDashboardData() { + const [system, dbStats] = await Promise.all([ + getSystemStatus(), + getDbStats(), + ]) + + return { ...system, db: dbStats } +} + +function getDbStats() { + try { + const db = getDatabase() + const now = Math.floor(Date.now() / 1000) + const day = now - 86400 + const week = now - 7 * 86400 + + // Task breakdown + const taskStats = db.prepare(` + SELECT status, COUNT(*) as count FROM tasks GROUP BY status + `).all() as Array<{ status: string; count: number }> + const tasksByStatus: Record = {} + let totalTasks = 0 + for (const row of taskStats) { + tasksByStatus[row.status] = row.count + totalTasks += row.count + } + + // Agent breakdown + const agentStats = db.prepare(` + SELECT status, COUNT(*) as count FROM agents GROUP BY status + `).all() as Array<{ status: string; count: number }> + const agentsByStatus: Record = {} + let totalAgents = 0 + for (const row of agentStats) { + agentsByStatus[row.status] = row.count + totalAgents += row.count + } + + // Audit events (24h / 7d) + const auditDay = (db.prepare('SELECT COUNT(*) as c FROM audit_log WHERE created_at > ?').get(day) as any).c + const auditWeek = (db.prepare('SELECT COUNT(*) as c FROM audit_log WHERE created_at > ?').get(week) as any).c + + // Security events (login failures in last 24h) + const loginFailures = (db.prepare( + "SELECT COUNT(*) as c FROM audit_log WHERE action = 'login_failed' AND created_at > ?" + ).get(day) as any).c + + // Activities (24h) + const activityDay = (db.prepare('SELECT COUNT(*) as c FROM activities WHERE created_at > ?').get(day) as any).c + + // Notifications (unread) + const unreadNotifs = (db.prepare('SELECT COUNT(*) as c FROM notifications WHERE read_at IS NULL').get() as any).c + + // Pipeline runs (active + recent) + let pipelineActive = 0 + let pipelineRecent = 0 + try { + pipelineActive = (db.prepare("SELECT COUNT(*) as c FROM pipeline_runs WHERE status = 'running'").get() as any).c + pipelineRecent = (db.prepare('SELECT COUNT(*) as c FROM pipeline_runs WHERE created_at > ?').get(day) as any).c + } catch { + // Pipeline tables may not exist yet + } + + // Latest backup + let latestBackup: { name: string; size: number; age_hours: number } | null = null + try { + const { readdirSync } = require('fs') + const { join, dirname } = require('path') + const backupDir = join(dirname(config.dbPath), 'backups') + const files = readdirSync(backupDir) + .filter((f: string) => f.endsWith('.db')) + .map((f: string) => { + const stat = statSync(join(backupDir, f)) + return { name: f, size: stat.size, mtime: stat.mtimeMs } + }) + .sort((a: any, b: any) => b.mtime - a.mtime) + if (files.length > 0) { + latestBackup = { + name: files[0].name, + size: files[0].size, + age_hours: Math.round((Date.now() - files[0].mtime) / 3600000), + } + } + } catch { + // No backups dir + } + + // DB file size + let dbSizeBytes = 0 + try { + dbSizeBytes = statSync(config.dbPath).size + } catch { + // ignore + } + + // Webhook configs count + let webhookCount = 0 + try { + webhookCount = (db.prepare('SELECT COUNT(*) as c FROM webhooks').get() as any).c + } catch { + // table may not exist + } + + return { + tasks: { total: totalTasks, byStatus: tasksByStatus }, + agents: { total: totalAgents, byStatus: agentsByStatus }, + audit: { day: auditDay, week: auditWeek, loginFailures }, + activities: { day: activityDay }, + notifications: { unread: unreadNotifs }, + pipelines: { active: pipelineActive, recentDay: pipelineRecent }, + backup: latestBackup, + dbSizeBytes, + webhookCount, + } + } catch (err) { + console.error('getDbStats error:', err) + return null + } +} + +async function getSystemStatus() { + const status: any = { + timestamp: Date.now(), + uptime: 0, + memory: { total: 0, used: 0, available: 0 }, + disk: { total: 0, used: 0, available: 0 }, + sessions: { total: 0, active: 0 }, + processes: [] + } + + try { + // System uptime + const { stdout: uptimeOutput } = await runCommand('uptime', ['-s'], { + timeoutMs: 3000 + }) + const bootTime = new Date(uptimeOutput.trim()) + status.uptime = Date.now() - bootTime.getTime() + } catch (error) { + console.error('Error getting uptime:', error) + } + + try { + // Memory info + const { stdout: memOutput } = await runCommand('free', ['-m'], { + timeoutMs: 3000 + }) + const memLines = memOutput.split('\n') + const memLine = memLines.find(line => line.startsWith('Mem:')) + if (memLine) { + const parts = memLine.split(/\s+/) + status.memory = { + total: parseInt(parts[1]) || 0, + used: parseInt(parts[2]) || 0, + available: parseInt(parts[6]) || 0 + } + } + } catch (error) { + console.error('Error getting memory info:', error) + } + + try { + // Disk info + const { stdout: diskOutput } = await runCommand('df', ['-h', '/'], { + timeoutMs: 3000 + }) + const lastLine = diskOutput.trim().split('\n').pop() || '' + const diskParts = lastLine.split(/\s+/) + if (diskParts.length >= 4) { + status.disk = { + total: diskParts[1], + used: diskParts[2], + available: diskParts[3], + usage: diskParts[4] + } + } + } catch (error) { + console.error('Error getting disk info:', error) + } + + try { + // ClawdBot processes + const { stdout: processOutput } = await runCommand( + 'ps', + ['-A', '-o', 'pid,comm,args'], + { timeoutMs: 3000 } + ) + const processes = processOutput.split('\n') + .filter(line => line.trim()) + .filter(line => !line.trim().toLowerCase().startsWith('pid ')) + .map(line => { + const parts = line.trim().split(/\s+/) + return { + pid: parts[0], + command: parts.slice(2).join(' ') + } + }) + .filter((proc) => /clawdbot|openclaw/i.test(proc.command)) + status.processes = processes + } catch (error) { + console.error('Error getting process info:', error) + } + + try { + // Read sessions directly from agent session stores on disk + const gatewaySessions = getAllGatewaySessions() + status.sessions = { + total: gatewaySessions.length, + active: gatewaySessions.filter((s) => s.active).length, + } + + // Sync agent statuses in DB from live session data + try { + const db = getDatabase() + const liveStatuses = getAgentLiveStatuses() + const now = Math.floor(Date.now() / 1000) + // Match by: exact name, lowercase, or normalized (spaces→hyphens) + const updateStmt = db.prepare( + `UPDATE agents SET status = ?, last_seen = ?, updated_at = ? + WHERE LOWER(name) = LOWER(?) + OR LOWER(REPLACE(name, ' ', '-')) = LOWER(?)` + ) + for (const [agentName, info] of liveStatuses) { + updateStmt.run( + info.status, + Math.floor(info.lastActivity / 1000), + now, + agentName, + agentName + ) + } + } catch (dbErr) { + console.error('Error syncing agent statuses:', dbErr) + } + } catch (error) { + console.error('Error reading session stores:', error) + } + + return status +} + +async function getGatewayStatus() { + const gatewayStatus: any = { + running: false, + port: config.gatewayPort, + pid: null, + uptime: 0, + version: null, + connections: 0 + } + + try { + const { stdout } = await runCommand('ps', ['-A', '-o', 'pid,comm,args'], { + timeoutMs: 3000 + }) + const match = stdout + .split('\n') + .find((line) => /clawdbot-gateway|openclaw-gateway|openclaw.*gateway/i.test(line)) + if (match) { + const parts = match.trim().split(/\s+/) + gatewayStatus.running = true + gatewayStatus.pid = parts[0] + } + } catch (error) { + // Gateway not running + } + + try { + gatewayStatus.port_listening = await isPortOpen(config.gatewayHost, config.gatewayPort) + } catch (error) { + console.error('Error checking port:', error) + } + + try { + const { stdout } = await runOpenClaw(['--version'], { timeoutMs: 3000 }) + gatewayStatus.version = stdout.trim() + } catch (error) { + try { + const { stdout } = await runClawdbot(['--version'], { timeoutMs: 3000 }) + gatewayStatus.version = stdout.trim() + } catch (innerError) { + gatewayStatus.version = 'unknown' + } + } + + return gatewayStatus +} + +async function getAvailableModels() { + // This would typically query the gateway or config files + // For now, return the models from AGENTS.md + const models = [ + { alias: 'haiku', name: 'anthropic/claude-3-5-haiku-latest', provider: 'anthropic', description: 'Ultra-cheap, simple tasks', costPer1k: 0.25 }, + { alias: 'sonnet', name: 'anthropic/claude-sonnet-4-20250514', provider: 'anthropic', description: 'Standard workhorse', costPer1k: 3.0 }, + { alias: 'opus', name: 'anthropic/claude-opus-4-5', provider: 'anthropic', description: 'Premium quality', costPer1k: 15.0 }, + { alias: 'deepseek', name: 'ollama/deepseek-r1:14b', provider: 'ollama', description: 'Local reasoning (free)', costPer1k: 0.0 }, + { alias: 'groq-fast', name: 'groq/llama-3.1-8b-instant', provider: 'groq', description: '840 tok/s, ultra fast', costPer1k: 0.05 }, + { alias: 'groq', name: 'groq/llama-3.3-70b-versatile', provider: 'groq', description: 'Fast + quality balance', costPer1k: 0.59 }, + { alias: 'kimi', name: 'moonshot/kimi-k2.5', provider: 'moonshot', description: 'Alternative provider', costPer1k: 1.0 }, + { alias: 'minimax', name: 'minimax/minimax-m2.1', provider: 'minimax', description: 'Cost-effective (1/10th price), strong coding', costPer1k: 0.3 }, + ] + + try { + // Check which Ollama models are available locally + const { stdout: ollamaOutput } = await runCommand('ollama', ['list'], { + timeoutMs: 5000 + }) + const ollamaModels = ollamaOutput.split('\n') + .slice(1) // Skip header + .filter(line => line.trim()) + .map(line => { + const parts = line.split(/\s+/) + return { + alias: parts[0], + name: `ollama/${parts[0]}`, + provider: 'ollama', + description: 'Local model', + costPer1k: 0.0, + size: parts[1] || 'unknown' + } + }) + + // Add Ollama models that aren't already in the list + ollamaModels.forEach(model => { + if (!models.find(m => m.name === model.name)) { + models.push(model) + } + }) + } catch (error) { + console.error('Error checking Ollama models:', error) + } + + return models +} + +async function performHealthCheck() { + const health: any = { + overall: 'healthy', + checks: [], + timestamp: Date.now() + } + + // Check gateway connection + try { + const gatewayStatus = await getGatewayStatus() + health.checks.push({ + name: 'Gateway', + status: gatewayStatus.running ? 'healthy' : 'unhealthy', + message: gatewayStatus.running ? 'Gateway is running' : 'Gateway is not running' + }) + } catch (error) { + health.checks.push({ + name: 'Gateway', + status: 'error', + message: 'Failed to check gateway status' + }) + } + + // Check disk space + try { + const { stdout } = await runCommand('df', ['/', '--output=pcent'], { + timeoutMs: 3000 + }) + const lines = stdout.trim().split('\n') + const last = lines[lines.length - 1] || '' + const usagePercent = parseInt(last.replace('%', '').trim() || '0') + + health.checks.push({ + name: 'Disk Space', + status: usagePercent < 90 ? 'healthy' : usagePercent < 95 ? 'warning' : 'critical', + message: `Disk usage: ${usagePercent}%` + }) + } catch (error) { + health.checks.push({ + name: 'Disk Space', + status: 'error', + message: 'Failed to check disk space' + }) + } + + // Check memory usage + try { + const { stdout } = await runCommand('free', ['-m'], { timeoutMs: 3000 }) + const lines = stdout.split('\n') + const memLine = lines.find((line) => line.startsWith('Mem:')) + const parts = (memLine || '').split(/\s+/) + const total = parseInt(parts[1] || '0') + const available = parseInt(parts[6] || '0') + const usagePercent = Math.round(((total - available) / total) * 100) + + health.checks.push({ + name: 'Memory Usage', + status: usagePercent < 90 ? 'healthy' : usagePercent < 95 ? 'warning' : 'critical', + message: `Memory usage: ${usagePercent}%` + }) + } catch (error) { + health.checks.push({ + name: 'Memory Usage', + status: 'error', + message: 'Failed to check memory usage' + }) + } + + // Determine overall health + const hasError = health.checks.some((check: any) => check.status === 'error') + const hasCritical = health.checks.some((check: any) => check.status === 'critical') + const hasWarning = health.checks.some((check: any) => check.status === 'warning') + + if (hasError || hasCritical) { + health.overall = 'unhealthy' + } else if (hasWarning) { + health.overall = 'warning' + } + + return health +} + +function isPortOpen(host: string, port: number): Promise { + return new Promise((resolve) => { + const socket = new net.Socket() + const timeoutMs = 1500 + + const cleanup = () => { + socket.removeAllListeners() + socket.destroy() + } + + socket.setTimeout(timeoutMs) + + socket.once('connect', () => { + cleanup() + resolve(true) + }) + + socket.once('timeout', () => { + cleanup() + resolve(false) + }) + + socket.once('error', () => { + cleanup() + resolve(false) + }) + + socket.connect(port, host) + }) +} diff --git a/src/app/api/super/provision-jobs/[id]/route.ts b/src/app/api/super/provision-jobs/[id]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..6b2042ccf74ca94d4c0106e1e53efc085dbd8382 --- /dev/null +++ b/src/app/api/super/provision-jobs/[id]/route.ts @@ -0,0 +1,58 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getProvisionJob, transitionProvisionJobStatus, ProvisionJobAction } from '@/lib/super-admin' + +/** + * GET /api/super/provision-jobs/[id] - Get job details and events + */ +export async function GET( + request: NextRequest, + context: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const params = await context.params + const id = Number(params.id) + if (!Number.isInteger(id) || id <= 0) { + return NextResponse.json({ error: 'Invalid job id' }, { status: 400 }) + } + + const job = getProvisionJob(id) + if (!job) return NextResponse.json({ error: 'Job not found' }, { status: 404 }) + + return NextResponse.json({ job }) +} + +/** + * POST /api/super/provision-jobs/[id] - Change job approval state + * Body: { action: 'approve' | 'reject' | 'cancel', reason?: string } + */ +export async function POST( + request: NextRequest, + context: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const params = await context.params + const id = Number(params.id) + if (!Number.isInteger(id) || id <= 0) { + return NextResponse.json({ error: 'Invalid job id' }, { status: 400 }) + } + + try { + const body = await request.json().catch(() => ({})) + const action = String(body?.action || '') as ProvisionJobAction + const reason = body?.reason ? String(body.reason) : undefined + + if (!['approve', 'reject', 'cancel'].includes(action)) { + return NextResponse.json({ error: 'Invalid action. Use approve, reject, or cancel.' }, { status: 400 }) + } + + const job = transitionProvisionJobStatus(id, auth.user.username, action, reason) + return NextResponse.json({ job }) + } catch (error: any) { + return NextResponse.json({ error: error?.message || 'Failed to update provisioning job state' }, { status: 400 }) + } +} diff --git a/src/app/api/super/provision-jobs/[id]/run/route.ts b/src/app/api/super/provision-jobs/[id]/run/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..e8f87b44e778e88de63c416b791567611d20267a --- /dev/null +++ b/src/app/api/super/provision-jobs/[id]/run/route.ts @@ -0,0 +1,27 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { executeProvisionJob } from '@/lib/super-admin' + +/** + * POST /api/super/provision-jobs/[id]/run - Execute an approved provisioning job + */ +export async function POST( + request: NextRequest, + context: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const params = await context.params + const id = Number(params.id) + if (!Number.isInteger(id) || id <= 0) { + return NextResponse.json({ error: 'Invalid job id' }, { status: 400 }) + } + + try { + const job = await executeProvisionJob(id, auth.user.username) + return NextResponse.json({ job }) + } catch (error: any) { + return NextResponse.json({ error: error?.message || 'Failed to execute provisioning job' }, { status: 400 }) + } +} diff --git a/src/app/api/super/provision-jobs/route.ts b/src/app/api/super/provision-jobs/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..eb7999f3307fd75bc29e104a90dc5f4efeb1c673 --- /dev/null +++ b/src/app/api/super/provision-jobs/route.ts @@ -0,0 +1,74 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { getDatabase } from '@/lib/db' +import { listProvisionJobs } from '@/lib/super-admin' + +/** + * GET /api/super/provision-jobs - List provisioning jobs + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const { searchParams } = new URL(request.url) + const tenant_id = searchParams.get('tenant_id') + const status = searchParams.get('status') || undefined + const limit = parseInt(searchParams.get('limit') || '100', 10) + + const jobs = listProvisionJobs({ + tenant_id: tenant_id ? parseInt(tenant_id, 10) : undefined, + status, + limit, + }) + + return NextResponse.json({ jobs }) +} + +/** + * POST /api/super/provision-jobs - Queue an additional bootstrap/update job for an existing tenant + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + const tenantId = Number(body.tenant_id) + const dryRun = body.dry_run !== false + const jobType = String(body.job_type || 'bootstrap') + + if (!Number.isInteger(tenantId) || tenantId <= 0) { + return NextResponse.json({ error: 'tenant_id is required' }, { status: 400 }) + } + + if (!['bootstrap', 'update', 'decommission'].includes(jobType)) { + return NextResponse.json({ error: 'Invalid job_type' }, { status: 400 }) + } + + const tenant = db.prepare('SELECT * FROM tenants WHERE id = ?').get(tenantId) as any + if (!tenant) { + return NextResponse.json({ error: 'Tenant not found' }, { status: 404 }) + } + + const plan = body.plan_json && Array.isArray(body.plan_json) ? body.plan_json : [] + const result = db.prepare(` + INSERT INTO provision_jobs (tenant_id, job_type, status, dry_run, requested_by, request_json, plan_json, updated_at) + VALUES (?, ?, 'queued', ?, ?, ?, ?, (unixepoch())) + `).run( + tenantId, + jobType, + dryRun ? 1 : 0, + auth.user.username, + JSON.stringify(body.request_json || {}), + JSON.stringify(plan), + ) + + const id = Number(result.lastInsertRowid) + return NextResponse.json({ + job: db.prepare('SELECT * FROM provision_jobs WHERE id = ?').get(id), + }, { status: 201 }) + } catch (error: any) { + return NextResponse.json({ error: error?.message || 'Failed to queue job' }, { status: 500 }) + } +} diff --git a/src/app/api/super/tenants/[id]/decommission/route.ts b/src/app/api/super/tenants/[id]/decommission/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..43f199e33adf5178f2219edeb67e538aa1bf150e --- /dev/null +++ b/src/app/api/super/tenants/[id]/decommission/route.ts @@ -0,0 +1,35 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { createTenantDecommissionJob } from '@/lib/super-admin' + +/** + * POST /api/super/tenants/[id]/decommission + * Body: { dry_run?: boolean, remove_linux_user?: boolean, remove_state_dirs?: boolean, reason?: string } + */ +export async function POST( + request: NextRequest, + context: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + const params = await context.params + const tenantId = Number(params.id) + if (!Number.isInteger(tenantId) || tenantId <= 0) { + return NextResponse.json({ error: 'Invalid tenant id' }, { status: 400 }) + } + + try { + const body = await request.json().catch(() => ({})) + const created = createTenantDecommissionJob(tenantId, { + dry_run: body?.dry_run, + remove_linux_user: body?.remove_linux_user, + remove_state_dirs: body?.remove_state_dirs, + reason: body?.reason, + }, auth.user.username) + + return NextResponse.json(created, { status: 201 }) + } catch (error: any) { + return NextResponse.json({ error: error?.message || 'Failed to queue tenant decommission job' }, { status: 400 }) + } +} diff --git a/src/app/api/super/tenants/route.ts b/src/app/api/super/tenants/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..6815ac8de328b6df981d3d44868a7fee0752722d --- /dev/null +++ b/src/app/api/super/tenants/route.ts @@ -0,0 +1,32 @@ +import { NextRequest, NextResponse } from 'next/server' +import { requireRole } from '@/lib/auth' +import { createTenantAndBootstrapJob, listTenants } from '@/lib/super-admin' + +/** + * GET /api/super/tenants - List tenants and latest provisioning status + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + return NextResponse.json({ tenants: listTenants() }) +} + +/** + * POST /api/super/tenants - Create tenant and queue bootstrap job + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const body = await request.json() + const created = createTenantAndBootstrapJob(body, auth.user.username) + return NextResponse.json(created, { status: 201 }) + } catch (error: any) { + if (String(error?.message || '').includes('UNIQUE')) { + return NextResponse.json({ error: 'Tenant slug or linux user already exists' }, { status: 409 }) + } + return NextResponse.json({ error: error?.message || 'Failed to create tenant bootstrap job' }, { status: 400 }) + } +} diff --git a/src/app/api/tasks/[id]/broadcast/route.ts b/src/app/api/tasks/[id]/broadcast/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..e0721587911466c46bedafd913e3a51dfa2933d1 --- /dev/null +++ b/src/app/api/tasks/[id]/broadcast/route.ts @@ -0,0 +1,92 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers } from '@/lib/db' +import { runOpenClaw } from '@/lib/command' +import { requireRole } from '@/lib/auth' + +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const resolvedParams = await params + const taskId = parseInt(resolvedParams.id) + const body = await request.json() + const author = (body.author || 'system') as string + const message = (body.message || '').trim() + + if (isNaN(taskId)) { + return NextResponse.json({ error: 'Invalid task ID' }, { status: 400 }) + } + if (!message) { + return NextResponse.json({ error: 'Message is required' }, { status: 400 }) + } + + const db = getDatabase() + const task = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId) as any + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }) + } + + const subscribers = new Set(db_helpers.getTaskSubscribers(taskId)) + subscribers.delete(author) + + if (subscribers.size === 0) { + return NextResponse.json({ sent: 0, skipped: 0 }) + } + + const agents = db + .prepare('SELECT name, session_key FROM agents WHERE name IN (' + Array.from(subscribers).map(() => '?').join(',') + ')') + .all(...Array.from(subscribers)) as Array<{ name: string; session_key?: string }> + + let sent = 0 + let skipped = 0 + + for (const agent of agents) { + if (!agent.session_key) { + skipped += 1 + continue + } + try { + await runOpenClaw( + [ + 'gateway', + 'sessions_send', + '--session', + agent.session_key, + '--message', + `[Task ${task.id}] ${task.title}\nFrom ${author}: ${message}` + ], + { timeoutMs: 10000 } + ) + sent += 1 + db_helpers.createNotification( + agent.name, + 'message', + 'Task Broadcast', + `${author} broadcasted a message on "${task.title}": ${message.substring(0, 100)}${message.length > 100 ? '...' : ''}`, + 'task', + taskId + ) + } catch (error) { + skipped += 1 + } + } + + db_helpers.logActivity( + 'task_broadcast', + 'task', + taskId, + author, + `Broadcasted message to ${sent} subscribers`, + { sent, skipped } + ) + + return NextResponse.json({ sent, skipped }) + } catch (error) { + console.error('POST /api/tasks/[id]/broadcast error:', error) + return NextResponse.json({ error: 'Failed to broadcast message' }, { status: 500 }) + } +} diff --git a/src/app/api/tasks/[id]/comments/route.ts b/src/app/api/tasks/[id]/comments/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..32de528b4aa1b667d9d486ad8b570abcfeb1853c --- /dev/null +++ b/src/app/api/tasks/[id]/comments/route.ts @@ -0,0 +1,202 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, Comment, db_helpers } from '@/lib/db'; +import { requireRole } from '@/lib/auth'; + +/** + * GET /api/tasks/[id]/comments - Get all comments for a task + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const db = getDatabase(); + const resolvedParams = await params; + const taskId = parseInt(resolvedParams.id); + + if (isNaN(taskId)) { + return NextResponse.json({ error: 'Invalid task ID' }, { status: 400 }); + } + + // Verify task exists + const task = db.prepare('SELECT id FROM tasks WHERE id = ?').get(taskId); + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + // Get comments ordered by creation time + const stmt = db.prepare(` + SELECT * FROM comments + WHERE task_id = ? + ORDER BY created_at ASC + `); + + const comments = stmt.all(taskId) as Comment[]; + + // Parse JSON fields and build thread structure + const commentsWithParsedData = comments.map(comment => ({ + ...comment, + mentions: comment.mentions ? JSON.parse(comment.mentions) : [] + })); + + // Organize into thread structure (parent comments with replies) + const commentMap = new Map(); + const topLevelComments: any[] = []; + + // First pass: create all comment objects + commentsWithParsedData.forEach(comment => { + commentMap.set(comment.id, { ...comment, replies: [] }); + }); + + // Second pass: organize into threads + commentsWithParsedData.forEach(comment => { + const commentWithReplies = commentMap.get(comment.id); + + if (comment.parent_id) { + // This is a reply, add to parent's replies + const parent = commentMap.get(comment.parent_id); + if (parent) { + parent.replies.push(commentWithReplies); + } + } else { + // This is a top-level comment + topLevelComments.push(commentWithReplies); + } + }); + + return NextResponse.json({ + comments: topLevelComments, + total: comments.length + }); + } catch (error) { + console.error(`GET /api/tasks/[id]/comments error:`, error); + return NextResponse.json({ error: 'Failed to fetch comments' }, { status: 500 }); + } +} + +/** + * POST /api/tasks/[id]/comments - Add a new comment to a task + */ +export async function POST( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const resolvedParams = await params; + const taskId = parseInt(resolvedParams.id); + const body = await request.json(); + + if (isNaN(taskId)) { + return NextResponse.json({ error: 'Invalid task ID' }, { status: 400 }); + } + + const { content, author = 'system', parent_id } = body; + + if (!content || !content.trim()) { + return NextResponse.json({ error: 'Comment content is required' }, { status: 400 }); + } + + // Verify task exists + const task = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId) as any; + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + // Verify parent comment exists if specified + if (parent_id) { + const parentComment = db.prepare('SELECT id FROM comments WHERE id = ? AND task_id = ?').get(parent_id, taskId); + if (!parentComment) { + return NextResponse.json({ error: 'Parent comment not found' }, { status: 404 }); + } + } + + // Parse @mentions from content + const mentions = db_helpers.parseMentions(content); + + const now = Math.floor(Date.now() / 1000); + + // Insert comment + const stmt = db.prepare(` + INSERT INTO comments (task_id, author, content, created_at, parent_id, mentions) + VALUES (?, ?, ?, ?, ?, ?) + `); + + const result = stmt.run( + taskId, + author, + content, + now, + parent_id || null, + mentions.length > 0 ? JSON.stringify(mentions) : null + ); + + const commentId = result.lastInsertRowid as number; + + // Log activity + const activityDescription = parent_id + ? `Replied to comment on task: ${task.title}` + : `Added comment to task: ${task.title}`; + + db_helpers.logActivity( + 'comment_added', + 'comment', + commentId, + author, + activityDescription, + { + task_id: taskId, + task_title: task.title, + parent_id, + mentions, + content_preview: content.substring(0, 100) + } + ); + + // Ensure subscriptions for author, mentions, and assignee + db_helpers.ensureTaskSubscription(taskId, author); + const uniqueMentions = Array.from(new Set(mentions)); + uniqueMentions.forEach((mentionedAgent) => { + db_helpers.ensureTaskSubscription(taskId, mentionedAgent); + }); + if (task.assigned_to) { + db_helpers.ensureTaskSubscription(taskId, task.assigned_to); + } + + // Notify subscribers + const subscribers = new Set(db_helpers.getTaskSubscribers(taskId)); + subscribers.delete(author); + const mentionSet = new Set(uniqueMentions); + + for (const subscriber of subscribers) { + const isMention = mentionSet.has(subscriber); + db_helpers.createNotification( + subscriber, + isMention ? 'mention' : 'comment', + isMention ? 'You were mentioned' : 'New comment on a subscribed task', + isMention + ? `${author} mentioned you in a comment on "${task.title}": ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}` + : `${author} commented on "${task.title}": ${content.substring(0, 100)}${content.length > 100 ? '...' : ''}`, + 'comment', + commentId + ); + } + + // Fetch the created comment + const createdComment = db.prepare('SELECT * FROM comments WHERE id = ?').get(commentId) as Comment; + + return NextResponse.json({ + comment: { + ...createdComment, + mentions: createdComment.mentions ? JSON.parse(createdComment.mentions) : [], + replies: [] // New comments have no replies initially + } + }, { status: 201 }); + } catch (error) { + console.error(`POST /api/tasks/[id]/comments error:`, error); + return NextResponse.json({ error: 'Failed to add comment' }, { status: 500 }); + } +} diff --git a/src/app/api/tasks/[id]/route.ts b/src/app/api/tasks/[id]/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..23b0ada61bbf0611d9ae86ebf122d84b13daea82 --- /dev/null +++ b/src/app/api/tasks/[id]/route.ts @@ -0,0 +1,297 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, Task, db_helpers } from '@/lib/db'; +import { eventBus } from '@/lib/event-bus'; +import { getUserFromRequest, requireRole } from '@/lib/auth'; + +function hasAegisApproval(db: ReturnType, taskId: number): boolean { + const review = db.prepare(` + SELECT status FROM quality_reviews + WHERE task_id = ? AND reviewer = 'aegis' + ORDER BY created_at DESC + LIMIT 1 + `).get(taskId) as { status?: string } | undefined + return review?.status === 'approved' +} + +/** + * GET /api/tasks/[id] - Get a specific task + */ +export async function GET( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + try { + const db = getDatabase(); + const resolvedParams = await params; + const taskId = parseInt(resolvedParams.id); + + if (isNaN(taskId)) { + return NextResponse.json({ error: 'Invalid task ID' }, { status: 400 }); + } + + const stmt = db.prepare('SELECT * FROM tasks WHERE id = ?'); + const task = stmt.get(taskId) as Task; + + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + // Parse JSON fields + const taskWithParsedData = { + ...task, + tags: task.tags ? JSON.parse(task.tags) : [], + metadata: task.metadata ? JSON.parse(task.metadata) : {} + }; + + return NextResponse.json({ task: taskWithParsedData }); + } catch (error) { + console.error('GET /api/tasks/[id] error:', error); + return NextResponse.json({ error: 'Failed to fetch task' }, { status: 500 }); + } +} + +/** + * PUT /api/tasks/[id] - Update a specific task + */ +export async function PUT( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const resolvedParams = await params; + const taskId = parseInt(resolvedParams.id); + const body = await request.json(); + + if (isNaN(taskId)) { + return NextResponse.json({ error: 'Invalid task ID' }, { status: 400 }); + } + + // Get current task for comparison + const currentTask = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId) as Task; + + if (!currentTask) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + const { + title, + description, + status, + priority, + assigned_to, + due_date, + estimated_hours, + actual_hours, + tags, + metadata + } = body; + + const now = Math.floor(Date.now() / 1000); + + // Build dynamic update query + const fieldsToUpdate = []; + const updateParams: any[] = []; + + if (title !== undefined) { + fieldsToUpdate.push('title = ?'); + updateParams.push(title); + } + if (description !== undefined) { + fieldsToUpdate.push('description = ?'); + updateParams.push(description); + } + if (status !== undefined) { + if (status === 'done' && !hasAegisApproval(db, taskId)) { + return NextResponse.json( + { error: 'Aegis approval is required to move task to done.' }, + { status: 403 } + ) + } + fieldsToUpdate.push('status = ?'); + updateParams.push(status); + } + if (priority !== undefined) { + fieldsToUpdate.push('priority = ?'); + updateParams.push(priority); + } + if (assigned_to !== undefined) { + fieldsToUpdate.push('assigned_to = ?'); + updateParams.push(assigned_to); + } + if (due_date !== undefined) { + fieldsToUpdate.push('due_date = ?'); + updateParams.push(due_date); + } + if (estimated_hours !== undefined) { + fieldsToUpdate.push('estimated_hours = ?'); + updateParams.push(estimated_hours); + } + if (actual_hours !== undefined) { + fieldsToUpdate.push('actual_hours = ?'); + updateParams.push(actual_hours); + } + if (tags !== undefined) { + fieldsToUpdate.push('tags = ?'); + updateParams.push(JSON.stringify(tags)); + } + if (metadata !== undefined) { + fieldsToUpdate.push('metadata = ?'); + updateParams.push(JSON.stringify(metadata)); + } + + fieldsToUpdate.push('updated_at = ?'); + updateParams.push(now); + updateParams.push(taskId); + + if (fieldsToUpdate.length === 1) { // Only updated_at + return NextResponse.json({ error: 'No fields to update' }, { status: 400 }); + } + + const stmt = db.prepare(` + UPDATE tasks + SET ${fieldsToUpdate.join(', ')} + WHERE id = ? + `); + + stmt.run(...updateParams); + + // Track changes and log activities + const changes: string[] = []; + + if (status && status !== currentTask.status) { + changes.push(`status: ${currentTask.status} → ${status}`); + + // Create notification for status change if assigned + if (currentTask.assigned_to) { + db_helpers.createNotification( + currentTask.assigned_to, + 'status_change', + 'Task Status Updated', + `Task "${currentTask.title}" status changed to ${status}`, + 'task', + taskId + ); + } + } + + if (assigned_to !== undefined && assigned_to !== currentTask.assigned_to) { + changes.push(`assigned: ${currentTask.assigned_to || 'unassigned'} → ${assigned_to || 'unassigned'}`); + + // Create notification for new assignee + if (assigned_to) { + db_helpers.ensureTaskSubscription(taskId, assigned_to); + db_helpers.createNotification( + assigned_to, + 'assignment', + 'Task Assigned', + `You have been assigned to task: ${currentTask.title}`, + 'task', + taskId + ); + } + } + + if (title && title !== currentTask.title) { + changes.push('title updated'); + } + + if (priority && priority !== currentTask.priority) { + changes.push(`priority: ${currentTask.priority} → ${priority}`); + } + + // Log activity if there were meaningful changes + if (changes.length > 0) { + db_helpers.logActivity( + 'task_updated', + 'task', + taskId, + getUserFromRequest(request)?.username || 'system', + `Task updated: ${changes.join(', ')}`, + { + changes: changes, + oldValues: { + title: currentTask.title, + status: currentTask.status, + priority: currentTask.priority, + assigned_to: currentTask.assigned_to + }, + newValues: { title, status, priority, assigned_to } + } + ); + } + + // Fetch updated task + const updatedTask = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId) as Task; + const parsedTask = { + ...updatedTask, + tags: updatedTask.tags ? JSON.parse(updatedTask.tags) : [], + metadata: updatedTask.metadata ? JSON.parse(updatedTask.metadata) : {} + }; + + // Broadcast to SSE clients + eventBus.broadcast('task.updated', parsedTask); + + return NextResponse.json({ task: parsedTask }); + } catch (error) { + console.error('PUT /api/tasks/[id] error:', error); + return NextResponse.json({ error: 'Failed to update task' }, { status: 500 }); + } +} + +/** + * DELETE /api/tasks/[id] - Delete a specific task + */ +export async function DELETE( + request: NextRequest, + { params }: { params: Promise<{ id: string }> } +) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const resolvedParams = await params; + const taskId = parseInt(resolvedParams.id); + + if (isNaN(taskId)) { + return NextResponse.json({ error: 'Invalid task ID' }, { status: 400 }); + } + + // Get task before deletion for logging + const task = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId) as Task; + + if (!task) { + return NextResponse.json({ error: 'Task not found' }, { status: 404 }); + } + + // Delete task (cascades will handle comments) + const stmt = db.prepare('DELETE FROM tasks WHERE id = ?'); + stmt.run(taskId); + + // Log deletion + db_helpers.logActivity( + 'task_deleted', + 'task', + taskId, + getUserFromRequest(request)?.username || 'system', + `Deleted task: ${task.title}`, + { + title: task.title, + status: task.status, + assigned_to: task.assigned_to + } + ); + + // Broadcast to SSE clients + eventBus.broadcast('task.deleted', { id: taskId, title: task.title }); + + return NextResponse.json({ success: true }); + } catch (error) { + console.error('DELETE /api/tasks/[id] error:', error); + return NextResponse.json({ error: 'Failed to delete task' }, { status: 500 }); + } +} diff --git a/src/app/api/tasks/route.ts b/src/app/api/tasks/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..40567c5c592ebd449f6009525148354bf6bf3768 --- /dev/null +++ b/src/app/api/tasks/route.ts @@ -0,0 +1,244 @@ +import { NextRequest, NextResponse } from 'next/server'; +import { getDatabase, Task, db_helpers } from '@/lib/db'; +import { eventBus } from '@/lib/event-bus'; +import { requireRole } from '@/lib/auth'; + +function hasAegisApproval(db: ReturnType, taskId: number): boolean { + const review = db.prepare(` + SELECT status FROM quality_reviews + WHERE task_id = ? AND reviewer = 'aegis' + ORDER BY created_at DESC + LIMIT 1 + `).get(taskId) as { status?: string } | undefined + return review?.status === 'approved' +} + +/** + * GET /api/tasks - List all tasks with optional filtering + * Query params: status, assigned_to, priority, limit, offset + */ +export async function GET(request: NextRequest) { + try { + const db = getDatabase(); + const { searchParams } = new URL(request.url); + + // Parse query parameters + const status = searchParams.get('status'); + const assigned_to = searchParams.get('assigned_to'); + const priority = searchParams.get('priority'); + const limit = parseInt(searchParams.get('limit') || '50'); + const offset = parseInt(searchParams.get('offset') || '0'); + + // Build dynamic query + let query = 'SELECT * FROM tasks WHERE 1=1'; + const params: any[] = []; + + if (status) { + query += ' AND status = ?'; + params.push(status); + } + + if (assigned_to) { + query += ' AND assigned_to = ?'; + params.push(assigned_to); + } + + if (priority) { + query += ' AND priority = ?'; + params.push(priority); + } + + query += ' ORDER BY created_at DESC LIMIT ? OFFSET ?'; + params.push(limit, offset); + + const stmt = db.prepare(query); + const tasks = stmt.all(...params) as Task[]; + + // Parse JSON fields + const tasksWithParsedData = tasks.map(task => ({ + ...task, + tags: task.tags ? JSON.parse(task.tags) : [], + metadata: task.metadata ? JSON.parse(task.metadata) : {} + })); + + return NextResponse.json({ tasks: tasksWithParsedData, total: tasks.length }); + } catch (error) { + console.error('GET /api/tasks error:', error); + return NextResponse.json({ error: 'Failed to fetch tasks' }, { status: 500 }); + } +} + +/** + * POST /api/tasks - Create a new task + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const body = await request.json(); + + const user = auth.user + const { + title, + description, + status = 'inbox', + priority = 'medium', + assigned_to, + created_by = user?.username || 'system', + due_date, + estimated_hours, + tags = [], + metadata = {} + } = body; + + if (!title) { + return NextResponse.json({ error: 'Title is required' }, { status: 400 }); + } + + // Check for duplicate title + const existingTask = db.prepare('SELECT id FROM tasks WHERE title = ?').get(title); + if (existingTask) { + return NextResponse.json({ error: 'Task with this title already exists' }, { status: 409 }); + } + + const now = Math.floor(Date.now() / 1000); + + const stmt = db.prepare(` + INSERT INTO tasks ( + title, description, status, priority, assigned_to, created_by, + created_at, updated_at, due_date, estimated_hours, tags, metadata + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) + `); + + const result = stmt.run( + title, + description, + status, + priority, + assigned_to, + created_by, + now, + now, + due_date, + estimated_hours, + JSON.stringify(tags), + JSON.stringify(metadata) + ); + + const taskId = result.lastInsertRowid as number; + + // Log activity + db_helpers.logActivity('task_created', 'task', taskId, created_by, `Created task: ${title}`, { + title, + status, + priority, + assigned_to + }); + + if (created_by) { + db_helpers.ensureTaskSubscription(taskId, created_by) + } + + // Create notification if assigned + if (assigned_to) { + db_helpers.ensureTaskSubscription(taskId, assigned_to) + db_helpers.createNotification( + assigned_to, + 'assignment', + 'Task Assigned', + `You have been assigned to task: ${title}`, + 'task', + taskId + ); + } + + // Fetch the created task + const createdTask = db.prepare('SELECT * FROM tasks WHERE id = ?').get(taskId) as Task; + const parsedTask = { + ...createdTask, + tags: JSON.parse(createdTask.tags || '[]'), + metadata: JSON.parse(createdTask.metadata || '{}') + }; + + // Broadcast to SSE clients + eventBus.broadcast('task.created', parsedTask); + + return NextResponse.json({ task: parsedTask }, { status: 201 }); + } catch (error) { + console.error('POST /api/tasks error:', error); + return NextResponse.json({ error: 'Failed to create task' }, { status: 500 }); + } +} + +/** + * PUT /api/tasks - Update multiple tasks (for drag-and-drop status changes) + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'operator'); + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }); + + try { + const db = getDatabase(); + const { tasks } = await request.json(); + + if (!Array.isArray(tasks)) { + return NextResponse.json({ error: 'Tasks must be an array' }, { status: 400 }); + } + + const now = Math.floor(Date.now() / 1000); + + const updateStmt = db.prepare(` + UPDATE tasks + SET status = ?, updated_at = ? + WHERE id = ? + `); + + const actor = auth.user.username + + const transaction = db.transaction((tasksToUpdate: any[]) => { + for (const task of tasksToUpdate) { + const oldTask = db.prepare('SELECT * FROM tasks WHERE id = ?').get(task.id) as Task; + + if (task.status === 'done' && !hasAegisApproval(db, task.id)) { + throw new Error(`Aegis approval required for task ${task.id}`) + } + + updateStmt.run(task.status, now, task.id); + + // Log status change if different + if (oldTask && oldTask.status !== task.status) { + db_helpers.logActivity( + 'task_updated', + 'task', + task.id, + actor, + `Task moved from ${oldTask.status} to ${task.status}`, + { oldStatus: oldTask.status, newStatus: task.status } + ); + } + } + }); + + transaction(tasks); + + // Broadcast status changes to SSE clients + for (const task of tasks) { + eventBus.broadcast('task.status_changed', { + id: task.id, + status: task.status, + updated_at: Math.floor(Date.now() / 1000), + }); + } + + return NextResponse.json({ success: true, updated: tasks.length }); + } catch (error) { + console.error('PUT /api/tasks error:', error); + const message = error instanceof Error ? error.message : 'Failed to update tasks' + if (message.includes('Aegis approval required')) { + return NextResponse.json({ error: message }, { status: 403 }); + } + return NextResponse.json({ error: 'Failed to update tasks' }, { status: 500 }); + } +} diff --git a/src/app/api/tokens/route.ts b/src/app/api/tokens/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..06bde54ffa6daa0e2021ed5438e15c15cce94223 --- /dev/null +++ b/src/app/api/tokens/route.ts @@ -0,0 +1,364 @@ +import { NextRequest, NextResponse } from 'next/server' +import { readFile, writeFile, access } from 'fs/promises' +import { dirname } from 'path' +import { config, ensureDirExists } from '@/lib/config' +import { requireRole } from '@/lib/auth' +import { getAllGatewaySessions } from '@/lib/sessions' + +const DATA_PATH = config.tokensPath + +interface TokenUsageRecord { + id: string + model: string + sessionId: string + timestamp: number + inputTokens: number + outputTokens: number + totalTokens: number + cost: number + operation: string + duration?: number +} + +interface TokenStats { + totalTokens: number + totalCost: number + requestCount: number + avgTokensPerRequest: number + avgCostPerRequest: number +} + +interface ExportData { + usage: TokenUsageRecord[] + summary: TokenStats + models: Record + sessions: Record +} + +// Model pricing (cost per 1K tokens) +const MODEL_PRICING: Record = { + 'anthropic/claude-3-5-haiku-latest': 0.25, + 'claude-3-5-haiku': 0.25, + 'anthropic/claude-sonnet-4-20250514': 3.0, + 'claude-sonnet-4': 3.0, + 'anthropic/claude-opus-4-5': 15.0, + 'claude-opus-4-5': 15.0, + 'groq/llama-3.1-8b-instant': 0.05, + 'groq/llama-3.3-70b-versatile': 0.59, + 'moonshot/kimi-k2.5': 1.0, + 'minimax/minimax-m2.1': 0.3, + 'ollama/deepseek-r1:14b': 0.0, + 'ollama/qwen2.5-coder:7b': 0.0, + 'ollama/qwen2.5-coder:14b': 0.0, +} + +function getModelCost(modelName: string): number { + if (MODEL_PRICING[modelName] !== undefined) return MODEL_PRICING[modelName] + for (const [model, cost] of Object.entries(MODEL_PRICING)) { + if (modelName.includes(model.split('/').pop() || '')) return cost + } + return 1.0 +} + +/** + * Load token data from persistent file, falling back to deriving from session stores. + */ +async function loadTokenData(): Promise { + // First try loading from persistent token file + try { + ensureDirExists(dirname(DATA_PATH)) + await access(DATA_PATH) + const data = await readFile(DATA_PATH, 'utf-8') + const records = JSON.parse(data) + if (Array.isArray(records) && records.length > 0) { + return records + } + } catch { + // File doesn't exist or is empty — derive from sessions + } + + // Derive token usage from session stores + return deriveFromSessions() +} + +/** + * Derive token usage records from OpenClaw session stores. + * Each session has totalTokens, inputTokens, outputTokens, model, etc. + */ +function deriveFromSessions(): TokenUsageRecord[] { + const sessions = getAllGatewaySessions(Infinity) // Get ALL sessions regardless of age + const records: TokenUsageRecord[] = [] + + for (const session of sessions) { + if (!session.totalTokens && !session.model) continue // Skip empty sessions + + const totalTokens = session.totalTokens || 0 + const inputTokens = session.inputTokens || Math.round(totalTokens * 0.7) + const outputTokens = session.outputTokens || totalTokens - inputTokens + const costPer1k = getModelCost(session.model || '') + const cost = (totalTokens / 1000) * costPer1k + + records.push({ + id: `session-${session.agent}-${session.key}`, + model: session.model || 'unknown', + sessionId: `${session.agent}:${session.chatType}`, + timestamp: session.updatedAt, + inputTokens, + outputTokens, + totalTokens, + cost, + operation: session.chatType || 'chat', + }) + } + + records.sort((a, b) => b.timestamp - a.timestamp) + return records +} + +async function saveTokenData(data: TokenUsageRecord[]): Promise { + ensureDirExists(dirname(DATA_PATH)) + await writeFile(DATA_PATH, JSON.stringify(data, null, 2)) +} + +function calculateStats(records: TokenUsageRecord[]): TokenStats { + if (records.length === 0) { + return { + totalTokens: 0, + totalCost: 0, + requestCount: 0, + avgTokensPerRequest: 0, + avgCostPerRequest: 0, + } + } + + const totalTokens = records.reduce((sum, r) => sum + r.totalTokens, 0) + const totalCost = records.reduce((sum, r) => sum + r.cost, 0) + const requestCount = records.length + + return { + totalTokens, + totalCost, + requestCount, + avgTokensPerRequest: Math.round(totalTokens / requestCount), + avgCostPerRequest: totalCost / requestCount, + } +} + +function filterByTimeframe(records: TokenUsageRecord[], timeframe: string): TokenUsageRecord[] { + const now = Date.now() + let cutoffTime: number + + switch (timeframe) { + case 'hour': + cutoffTime = now - 60 * 60 * 1000 + break + case 'day': + cutoffTime = now - 24 * 60 * 60 * 1000 + break + case 'week': + cutoffTime = now - 7 * 24 * 60 * 60 * 1000 + break + case 'month': + cutoffTime = now - 30 * 24 * 60 * 60 * 1000 + break + case 'all': + default: + return records + } + + return records.filter(record => record.timestamp >= cutoffTime) +} + +export async function GET(request: NextRequest) { + try { + const { searchParams } = new URL(request.url) + const action = searchParams.get('action') || 'list' + const timeframe = searchParams.get('timeframe') || 'all' + const format = searchParams.get('format') || 'json' + + const tokenData = await loadTokenData() + const filteredData = filterByTimeframe(tokenData, timeframe) + + if (action === 'list') { + return NextResponse.json({ + usage: filteredData.slice(0, 100), + total: filteredData.length, + timeframe, + }) + } + + if (action === 'stats') { + const overallStats = calculateStats(filteredData) + + const modelGroups = filteredData.reduce((acc, record) => { + if (!acc[record.model]) acc[record.model] = [] + acc[record.model].push(record) + return acc + }, {} as Record) + + const modelStats: Record = {} + for (const [model, records] of Object.entries(modelGroups)) { + modelStats[model] = calculateStats(records) + } + + const sessionGroups = filteredData.reduce((acc, record) => { + if (!acc[record.sessionId]) acc[record.sessionId] = [] + acc[record.sessionId].push(record) + return acc + }, {} as Record) + + const sessionStats: Record = {} + for (const [sessionId, records] of Object.entries(sessionGroups)) { + sessionStats[sessionId] = calculateStats(records) + } + + return NextResponse.json({ + summary: overallStats, + models: modelStats, + sessions: sessionStats, + timeframe, + recordCount: filteredData.length, + }) + } + + if (action === 'export') { + const overallStats = calculateStats(filteredData) + const modelStats: Record = {} + const sessionStats: Record = {} + + const modelGroups = filteredData.reduce((acc, record) => { + if (!acc[record.model]) acc[record.model] = [] + acc[record.model].push(record) + return acc + }, {} as Record) + + for (const [model, records] of Object.entries(modelGroups)) { + modelStats[model] = calculateStats(records) + } + + const sessionGroups = filteredData.reduce((acc, record) => { + if (!acc[record.sessionId]) acc[record.sessionId] = [] + acc[record.sessionId].push(record) + return acc + }, {} as Record) + + for (const [sessionId, records] of Object.entries(sessionGroups)) { + sessionStats[sessionId] = calculateStats(records) + } + + const exportData: ExportData = { + usage: filteredData, + summary: overallStats, + models: modelStats, + sessions: sessionStats, + } + + if (format === 'csv') { + const headers = ['timestamp', 'model', 'sessionId', 'operation', 'inputTokens', 'outputTokens', 'totalTokens', 'cost', 'duration'] + const csvRows = [headers.join(',')] + + filteredData.forEach(record => { + csvRows.push([ + new Date(record.timestamp).toISOString(), + record.model, + record.sessionId, + record.operation, + record.inputTokens, + record.outputTokens, + record.totalTokens, + record.cost.toFixed(4), + record.duration || 0, + ].join(',')) + }) + + return new NextResponse(csvRows.join('\n'), { + headers: { + 'Content-Type': 'text/csv', + 'Content-Disposition': `attachment; filename=token-usage-${timeframe}-${new Date().toISOString().split('T')[0]}.csv`, + }, + }) + } + + return NextResponse.json(exportData, { + headers: { + 'Content-Type': 'application/json', + 'Content-Disposition': `attachment; filename=token-usage-${timeframe}-${new Date().toISOString().split('T')[0]}.json`, + }, + }) + } + + if (action === 'trends') { + const now = Date.now() + const twentyFourHoursAgo = now - 24 * 60 * 60 * 1000 + const recentData = filteredData.filter(r => r.timestamp >= twentyFourHoursAgo) + + const hourlyTrends: Record = {} + + recentData.forEach(record => { + const hour = new Date(record.timestamp).toISOString().slice(0, 13) + ':00:00.000Z' + if (!hourlyTrends[hour]) { + hourlyTrends[hour] = { tokens: 0, cost: 0, requests: 0 } + } + hourlyTrends[hour].tokens += record.totalTokens + hourlyTrends[hour].cost += record.cost + hourlyTrends[hour].requests += 1 + }) + + const trends = Object.entries(hourlyTrends) + .sort(([a], [b]) => a.localeCompare(b)) + .map(([timestamp, data]) => ({ timestamp, ...data })) + + return NextResponse.json({ trends, timeframe }) + } + + return NextResponse.json({ error: 'Invalid action' }, { status: 400 }) + } catch (error) { + console.error('Tokens API error:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} + +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const body = await request.json() + const { model, sessionId, inputTokens, outputTokens, operation = 'chat_completion', duration } = body + + if (!model || !sessionId || typeof inputTokens !== 'number' || typeof outputTokens !== 'number') { + return NextResponse.json({ error: 'Missing required fields' }, { status: 400 }) + } + + const totalTokens = inputTokens + outputTokens + const costPer1k = getModelCost(model) + const cost = (totalTokens / 1000) * costPer1k + + const record: TokenUsageRecord = { + id: `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, + model, + sessionId, + timestamp: Date.now(), + inputTokens, + outputTokens, + totalTokens, + cost, + operation, + duration, + } + + const existingData = await loadTokenData() + existingData.unshift(record) + + if (existingData.length > 10000) { + existingData.splice(10000) + } + + await saveTokenData(existingData) + + return NextResponse.json({ success: true, record }) + } catch (error) { + console.error('Error saving token usage:', error) + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }) + } +} diff --git a/src/app/api/webhooks/deliveries/route.ts b/src/app/api/webhooks/deliveries/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..becd60fc8c68df827fc98a3e9589689b45e3fab5 --- /dev/null +++ b/src/app/api/webhooks/deliveries/route.ts @@ -0,0 +1,50 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase } from '@/lib/db' +import { requireRole } from '@/lib/auth' + +/** + * GET /api/webhooks/deliveries - Get delivery history for a webhook + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + const webhookId = searchParams.get('webhook_id') + const limit = parseInt(searchParams.get('limit') || '50') + const offset = parseInt(searchParams.get('offset') || '0') + + let query = ` + SELECT wd.*, w.name as webhook_name, w.url as webhook_url + FROM webhook_deliveries wd + JOIN webhooks w ON wd.webhook_id = w.id + ` + const params: any[] = [] + + if (webhookId) { + query += ' WHERE wd.webhook_id = ?' + params.push(webhookId) + } + + query += ' ORDER BY wd.created_at DESC LIMIT ? OFFSET ?' + params.push(limit, offset) + + const deliveries = db.prepare(query).all(...params) + + // Get total count + let countQuery = 'SELECT COUNT(*) as count FROM webhook_deliveries' + const countParams: any[] = [] + if (webhookId) { + countQuery += ' WHERE webhook_id = ?' + countParams.push(webhookId) + } + const { count: total } = db.prepare(countQuery).get(...countParams) as { count: number } + + return NextResponse.json({ deliveries, total }) + } catch (error) { + console.error('GET /api/webhooks/deliveries error:', error) + return NextResponse.json({ error: 'Failed to fetch deliveries' }, { status: 500 }) + } +} diff --git a/src/app/api/webhooks/route.ts b/src/app/api/webhooks/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..5dd91293c5b6fad2b5a6291a83ba5d8ecacd405a --- /dev/null +++ b/src/app/api/webhooks/route.ts @@ -0,0 +1,169 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase } from '@/lib/db' +import { requireRole } from '@/lib/auth' +import { randomBytes, createHmac } from 'crypto' + +/** + * GET /api/webhooks - List all webhooks with delivery stats + */ +export async function GET(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const webhooks = db.prepare(` + SELECT w.*, + (SELECT COUNT(*) FROM webhook_deliveries wd WHERE wd.webhook_id = w.id) as total_deliveries, + (SELECT COUNT(*) FROM webhook_deliveries wd WHERE wd.webhook_id = w.id AND wd.status_code BETWEEN 200 AND 299) as successful_deliveries, + (SELECT COUNT(*) FROM webhook_deliveries wd WHERE wd.webhook_id = w.id AND (wd.error IS NOT NULL OR wd.status_code NOT BETWEEN 200 AND 299)) as failed_deliveries + FROM webhooks w + ORDER BY w.created_at DESC + `).all() as any[] + + // Parse events JSON, mask secret + const result = webhooks.map((wh) => ({ + ...wh, + events: JSON.parse(wh.events || '["*"]'), + secret: wh.secret ? '••••••' + wh.secret.slice(-4) : null, + enabled: !!wh.enabled, + })) + + return NextResponse.json({ webhooks: result }) + } catch (error) { + console.error('GET /api/webhooks error:', error) + return NextResponse.json({ error: 'Failed to fetch webhooks' }, { status: 500 }) + } +} + +/** + * POST /api/webhooks - Create a new webhook + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + const { name, url, events, generate_secret } = body + + if (!name || !url) { + return NextResponse.json({ error: 'Name and URL are required' }, { status: 400 }) + } + + // Validate URL + try { + new URL(url) + } catch { + return NextResponse.json({ error: 'Invalid URL' }, { status: 400 }) + } + + const secret = generate_secret !== false ? randomBytes(32).toString('hex') : null + const eventsJson = JSON.stringify(events || ['*']) + + const result = db.prepare(` + INSERT INTO webhooks (name, url, secret, events, created_by) + VALUES (?, ?, ?, ?, ?) + `).run(name, url, secret, eventsJson, auth.user.username) + + return NextResponse.json({ + id: result.lastInsertRowid, + name, + url, + secret, // Show full secret only on creation + events: events || ['*'], + enabled: true, + message: 'Webhook created. Save the secret - it won\'t be shown again in full.', + }) + } catch (error) { + console.error('POST /api/webhooks error:', error) + return NextResponse.json({ error: 'Failed to create webhook' }, { status: 500 }) + } +} + +/** + * PUT /api/webhooks - Update a webhook + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + const { id, name, url, events, enabled, regenerate_secret } = body + + if (!id) { + return NextResponse.json({ error: 'Webhook ID is required' }, { status: 400 }) + } + + const existing = db.prepare('SELECT * FROM webhooks WHERE id = ?').get(id) as any + if (!existing) { + return NextResponse.json({ error: 'Webhook not found' }, { status: 404 }) + } + + if (url) { + try { new URL(url) } catch { + return NextResponse.json({ error: 'Invalid URL' }, { status: 400 }) + } + } + + const updates: string[] = ['updated_at = unixepoch()'] + const params: any[] = [] + + if (name !== undefined) { updates.push('name = ?'); params.push(name) } + if (url !== undefined) { updates.push('url = ?'); params.push(url) } + if (events !== undefined) { updates.push('events = ?'); params.push(JSON.stringify(events)) } + if (enabled !== undefined) { updates.push('enabled = ?'); params.push(enabled ? 1 : 0) } + + let newSecret: string | null = null + if (regenerate_secret) { + newSecret = randomBytes(32).toString('hex') + updates.push('secret = ?') + params.push(newSecret) + } + + params.push(id) + db.prepare(`UPDATE webhooks SET ${updates.join(', ')} WHERE id = ?`).run(...params) + + return NextResponse.json({ + success: true, + ...(newSecret ? { secret: newSecret, message: 'New secret generated. Save it now.' } : {}), + }) + } catch (error) { + console.error('PUT /api/webhooks error:', error) + return NextResponse.json({ error: 'Failed to update webhook' }, { status: 500 }) + } +} + +/** + * DELETE /api/webhooks - Delete a webhook + */ +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + const id = searchParams.get('id') + + if (!id) { + return NextResponse.json({ error: 'Webhook ID is required' }, { status: 400 }) + } + + // Delete deliveries first (cascade should handle it, but be explicit) + db.prepare('DELETE FROM webhook_deliveries WHERE webhook_id = ?').run(id) + const result = db.prepare('DELETE FROM webhooks WHERE id = ?').run(id) + + if (result.changes === 0) { + return NextResponse.json({ error: 'Webhook not found' }, { status: 404 }) + } + + return NextResponse.json({ success: true, deleted: result.changes }) + } catch (error) { + console.error('DELETE /api/webhooks error:', error) + return NextResponse.json({ error: 'Failed to delete webhook' }, { status: 500 }) + } +} diff --git a/src/app/api/webhooks/test/route.ts b/src/app/api/webhooks/test/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..d27c6490dd2b8dc65fdc616448ddd238f1d95d6b --- /dev/null +++ b/src/app/api/webhooks/test/route.ts @@ -0,0 +1,100 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase } from '@/lib/db' +import { requireRole } from '@/lib/auth' +import { createHmac } from 'crypto' + +/** + * POST /api/webhooks/test - Send a test event to a webhook + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'admin') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const { id } = await request.json() + + if (!id) { + return NextResponse.json({ error: 'Webhook ID is required' }, { status: 400 }) + } + + const webhook = db.prepare('SELECT * FROM webhooks WHERE id = ?').get(id) as any + if (!webhook) { + return NextResponse.json({ error: 'Webhook not found' }, { status: 404 }) + } + + const body = JSON.stringify({ + event: 'test.ping', + timestamp: Math.floor(Date.now() / 1000), + data: { + message: 'This is a test webhook from Mission Control', + webhook_id: webhook.id, + webhook_name: webhook.name, + triggered_by: auth.user.username, + }, + }) + + const headers: Record = { + 'Content-Type': 'application/json', + 'User-Agent': 'MissionControl-Webhook/1.0', + 'X-MC-Event': 'test.ping', + } + + if (webhook.secret) { + const sig = createHmac('sha256', webhook.secret).update(body).digest('hex') + headers['X-MC-Signature'] = `sha256=${sig}` + } + + const start = Date.now() + let statusCode: number | null = null + let responseBody: string | null = null + let error: string | null = null + + try { + const controller = new AbortController() + const timeout = setTimeout(() => controller.abort(), 10000) + + const res = await fetch(webhook.url, { + method: 'POST', + headers, + body, + signal: controller.signal, + }) + + clearTimeout(timeout) + statusCode = res.status + responseBody = await res.text().catch(() => null) + if (responseBody && responseBody.length > 1000) { + responseBody = responseBody.slice(0, 1000) + '...' + } + } catch (err: any) { + error = err.name === 'AbortError' ? 'Timeout (10s)' : err.message + } + + const durationMs = Date.now() - start + + // Log the test delivery + db.prepare(` + INSERT INTO webhook_deliveries (webhook_id, event_type, payload, status_code, response_body, error, duration_ms) + VALUES (?, ?, ?, ?, ?, ?, ?) + `).run(webhook.id, 'test.ping', body, statusCode, responseBody, error, durationMs) + + db.prepare(` + UPDATE webhooks SET last_fired_at = unixepoch(), last_status = ?, updated_at = unixepoch() + WHERE id = ? + `).run(statusCode ?? -1, webhook.id) + + const success = statusCode !== null && statusCode >= 200 && statusCode < 300 + + return NextResponse.json({ + success, + status_code: statusCode, + response_body: responseBody, + error, + duration_ms: durationMs, + }) + } catch (error) { + console.error('POST /api/webhooks/test error:', error) + return NextResponse.json({ error: 'Failed to test webhook' }, { status: 500 }) + } +} diff --git a/src/app/api/workflows/route.ts b/src/app/api/workflows/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..d4f5ee5a16e406d185ca72dac1d784e8038bfcd1 --- /dev/null +++ b/src/app/api/workflows/route.ts @@ -0,0 +1,152 @@ +import { NextRequest, NextResponse } from 'next/server' +import { getDatabase, db_helpers } from '@/lib/db' +import { requireRole } from '@/lib/auth' + +export interface WorkflowTemplate { + id: number + name: string + description: string | null + model: string + task_prompt: string + timeout_seconds: number + agent_role: string | null + tags: string | null + created_by: string + created_at: number + updated_at: number + last_used_at: number | null + use_count: number +} + +/** + * GET /api/workflows - List all workflow templates + */ +export async function GET() { + try { + const db = getDatabase() + const templates = db.prepare('SELECT * FROM workflow_templates ORDER BY use_count DESC, updated_at DESC').all() as WorkflowTemplate[] + + const parsed = templates.map(t => ({ + ...t, + tags: t.tags ? JSON.parse(t.tags) : [], + })) + + return NextResponse.json({ templates: parsed }) + } catch (error) { + console.error('GET /api/workflows error:', error) + return NextResponse.json({ error: 'Failed to fetch templates' }, { status: 500 }) + } +} + +/** + * POST /api/workflows - Create a new workflow template + */ +export async function POST(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const user = auth.user + const body = await request.json() + + const { name, description, model = 'sonnet', task_prompt, timeout_seconds = 300, agent_role, tags = [] } = body + + if (!name || !task_prompt) { + return NextResponse.json({ error: 'Name and task_prompt are required' }, { status: 400 }) + } + + const result = db.prepare(` + INSERT INTO workflow_templates (name, description, model, task_prompt, timeout_seconds, agent_role, tags, created_by) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `).run(name, description || null, model, task_prompt, timeout_seconds, agent_role || null, JSON.stringify(tags), user?.username || 'system') + + const template = db.prepare('SELECT * FROM workflow_templates WHERE id = ?').get(result.lastInsertRowid) as WorkflowTemplate + + db_helpers.logActivity('workflow_created', 'workflow', Number(result.lastInsertRowid), user?.username || 'system', `Created workflow template: ${name}`) + + return NextResponse.json({ + template: { ...template, tags: template.tags ? JSON.parse(template.tags) : [] } + }, { status: 201 }) + } catch (error) { + console.error('POST /api/workflows error:', error) + return NextResponse.json({ error: 'Failed to create template' }, { status: 500 }) + } +} + +/** + * PUT /api/workflows - Update a workflow template + */ +export async function PUT(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const body = await request.json() + const { id, ...updates } = body + + if (!id) { + return NextResponse.json({ error: 'Template ID is required' }, { status: 400 }) + } + + const existing = db.prepare('SELECT * FROM workflow_templates WHERE id = ?').get(id) as WorkflowTemplate + if (!existing) { + return NextResponse.json({ error: 'Template not found' }, { status: 404 }) + } + + const fields: string[] = [] + const params: any[] = [] + + if (updates.name !== undefined) { fields.push('name = ?'); params.push(updates.name) } + if (updates.description !== undefined) { fields.push('description = ?'); params.push(updates.description) } + if (updates.model !== undefined) { fields.push('model = ?'); params.push(updates.model) } + if (updates.task_prompt !== undefined) { fields.push('task_prompt = ?'); params.push(updates.task_prompt) } + if (updates.timeout_seconds !== undefined) { fields.push('timeout_seconds = ?'); params.push(updates.timeout_seconds) } + if (updates.agent_role !== undefined) { fields.push('agent_role = ?'); params.push(updates.agent_role) } + if (updates.tags !== undefined) { fields.push('tags = ?'); params.push(JSON.stringify(updates.tags)) } + + // No explicit field updates = usage tracking call (from orchestration bar) + if (fields.length === 0) { + fields.push('use_count = use_count + 1') + fields.push('last_used_at = ?') + params.push(Math.floor(Date.now() / 1000)) + } + + fields.push('updated_at = ?') + params.push(Math.floor(Date.now() / 1000)) + params.push(id) + + db.prepare(`UPDATE workflow_templates SET ${fields.join(', ')} WHERE id = ?`).run(...params) + + const updated = db.prepare('SELECT * FROM workflow_templates WHERE id = ?').get(id) as WorkflowTemplate + return NextResponse.json({ template: { ...updated, tags: updated.tags ? JSON.parse(updated.tags) : [] } }) + } catch (error) { + console.error('PUT /api/workflows error:', error) + return NextResponse.json({ error: 'Failed to update template' }, { status: 500 }) + } +} + +/** + * DELETE /api/workflows - Delete a workflow template + */ +export async function DELETE(request: NextRequest) { + const auth = requireRole(request, 'operator') + if ('error' in auth) return NextResponse.json({ error: auth.error }, { status: auth.status }) + + try { + const db = getDatabase() + const { searchParams } = new URL(request.url) + const id = searchParams.get('id') + + if (!id) { + return NextResponse.json({ error: 'Template ID is required' }, { status: 400 }) + } + + db.prepare('DELETE FROM workflow_templates WHERE id = ?').run(parseInt(id)) + return NextResponse.json({ success: true }) + } catch (error) { + console.error('DELETE /api/workflows error:', error) + return NextResponse.json({ error: 'Failed to delete template' }, { status: 500 }) + } +} diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000000000000000000000000000000000000..2a32e9f4c4bfb1ab25c377e5ece448c9b431d1b3 --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,269 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + /* oklch-based color system for perceptual uniformity */ + --background: 0 0% 100%; + --foreground: 240 10% 3.9%; + --card: 0 0% 100%; + --card-foreground: 240 10% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 240 10% 3.9%; + --primary: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --secondary: 240 4.8% 95.9%; + --secondary-foreground: 240 5.9% 10%; + --muted: 240 4.8% 95.9%; + --muted-foreground: 240 3.8% 46.1%; + --accent: 240 4.8% 95.9%; + --accent-foreground: 240 5.9% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 240 5.9% 90%; + --input: 240 5.9% 90%; + --ring: 240 5.9% 10%; + --radius: 0.625rem; + + /* Semantic status colors */ + --success: 142 71% 45%; + --success-foreground: 0 0% 98%; + --warning: 38 92% 50%; + --warning-foreground: 0 0% 9%; + --info: 217 91% 60%; + --info-foreground: 0 0% 98%; + + /* Surface hierarchy */ + --surface-0: 0 0% 100%; + --surface-1: 240 5% 96%; + --surface-2: 240 6% 93%; + --surface-3: 240 5% 88%; + } + + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 95%; + --card: 240 10% 5.5%; + --card-foreground: 0 0% 95%; + --popover: 240 10% 5.5%; + --popover-foreground: 0 0% 95%; + --primary: 210 100% 52%; + --primary-foreground: 0 0% 100%; + --secondary: 240 5% 12%; + --secondary-foreground: 0 0% 95%; + --muted: 240 5% 15%; + --muted-foreground: 240 5% 55%; + --accent: 240 5% 15%; + --accent-foreground: 0 0% 95%; + --destructive: 0 63% 31%; + --destructive-foreground: 0 0% 98%; + --border: 240 4% 16%; + --input: 240 4% 16%; + --ring: 210 100% 52%; + + /* Dark mode status */ + --success: 142 71% 45%; + --success-foreground: 0 0% 98%; + --warning: 38 92% 50%; + --warning-foreground: 0 0% 9%; + --info: 217 91% 60%; + --info-foreground: 0 0% 98%; + + /* Dark surface hierarchy */ + --surface-0: 240 10% 3.9%; + --surface-1: 240 8% 6%; + --surface-2: 240 6% 9%; + --surface-3: 240 5% 13%; + } +} + +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + font-feature-settings: "rlig" 1, "calt" 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + } + + /* Custom scrollbar */ + ::-webkit-scrollbar { + width: 5px; + height: 5px; + } + + ::-webkit-scrollbar-track { + background: transparent; + } + + ::-webkit-scrollbar-thumb { + @apply bg-border rounded-full; + } + + ::-webkit-scrollbar-thumb:hover { + @apply bg-muted-foreground; + } +} + +@layer utilities { + /* Glass morphism effects */ + .glass { + @apply bg-card/80 backdrop-blur-xl border border-border/50; + } + + .glass-strong { + @apply bg-card/90 backdrop-blur-2xl border border-border/60; + } + + /* Surface levels */ + .surface-0 { + background: hsl(var(--surface-0)); + } + .surface-1 { + background: hsl(var(--surface-1)); + } + .surface-2 { + background: hsl(var(--surface-2)); + } + .surface-3 { + background: hsl(var(--surface-3)); + } + + /* Status badge styles */ + .badge-success { + @apply bg-green-500/15 text-green-400 border border-green-500/20; + } + .badge-warning { + @apply bg-amber-500/15 text-amber-400 border border-amber-500/20; + } + .badge-error { + @apply bg-red-500/15 text-red-400 border border-red-500/20; + } + .badge-info { + @apply bg-blue-500/15 text-blue-400 border border-blue-500/20; + } + .badge-neutral { + @apply bg-muted text-muted-foreground border border-border; + } + + /* Glow effects for active states */ + .glow-primary { + box-shadow: 0 0 20px hsl(var(--primary) / 0.15); + } + .glow-success { + box-shadow: 0 0 20px hsl(var(--success) / 0.15); + } + + /* Smooth transitions */ + .transition-smooth { + @apply transition-all duration-200 ease-out; + } + + /* Panel styles */ + .panel { + @apply bg-card border border-border rounded-lg; + } + + .panel-header { + @apply px-4 py-3 border-b border-border flex items-center justify-between; + } + + .panel-body { + @apply p-4; + } + + /* Mono/code font */ + .font-mono-tight { + font-family: 'JetBrains Mono', 'SF Mono', 'Fira Code', 'Cascadia Code', ui-monospace, monospace; + font-variant-numeric: tabular-nums; + } + + /* Digital clock styling */ + .digital-clock { + font-family: 'JetBrains Mono', 'SF Mono', ui-monospace, monospace; + font-weight: 600; + letter-spacing: 0.04em; + font-variant-numeric: tabular-nums; + } + + /* Pulse animations for status */ + .pulse-live { + animation: pulse-live 2s ease-in-out infinite; + } + + @keyframes pulse-live { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.5; } + } + + .pulse-dot { + animation: pulse-dot 2s ease-in-out infinite; + } + + @keyframes pulse-dot { + 0%, 100% { transform: scale(1); opacity: 1; } + 50% { transform: scale(1.3); opacity: 0.7; } + } + + /* Shimmer loading */ + .shimmer { + background: linear-gradient( + 90deg, + hsl(var(--muted)) 0%, + hsl(var(--muted-foreground) / 0.1) 50%, + hsl(var(--muted)) 100% + ); + background-size: 200% 100%; + animation: shimmer 1.5s ease-in-out infinite; + } + + @keyframes shimmer { + 0% { background-position: -200% 0; } + 100% { background-position: 200% 0; } + } + + /* Slide-in for panels */ + .slide-in-right { + animation: slideInRight 0.2s ease-out; + } + + @keyframes slideInRight { + from { transform: translateX(100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } + } + + .slide-in-left { + animation: slideInLeft 0.2s ease-out; + } + + @keyframes slideInLeft { + from { transform: translateX(-100%); opacity: 0; } + to { transform: translateX(0); opacity: 1; } + } + + .fade-in { + animation: fadeIn 0.15s ease-out; + } + + @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + + /* iOS safe area for bottom nav */ + .safe-area-bottom { + padding-bottom: env(safe-area-inset-bottom, 0px); + } + + /* Touch-friendly tap targets */ + @media (pointer: coarse) { + .touch-target { + min-height: 44px; + min-width: 44px; + } + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..111180f187cd1480a2d8fa55317c9aa88c4693a0 --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,43 @@ +import type { Metadata, Viewport } from 'next' +import { ThemeProvider } from 'next-themes' +import './globals.css' + +export const viewport: Viewport = { + width: 'device-width', + initialScale: 1, + maximumScale: 1, + viewportFit: 'cover', +} + +export const metadata: Metadata = { + title: 'Mission Control', + description: 'OpenClaw Agent Orchestration Dashboard', + appleWebApp: { + capable: true, + statusBarStyle: 'black-translucent', + title: 'Mission Control', + }, +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + +
+ {children} +
+
+ + + ) +} diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..e3a8ccc0e0451b336990f025cc1caf2ba6e39f81 --- /dev/null +++ b/src/app/login/page.tsx @@ -0,0 +1,182 @@ +'use client' + +import { useCallback, useEffect, useRef, useState, FormEvent } from 'react' +import { useRouter } from 'next/navigation' + +declare global { + interface Window { + google?: any + } +} + +export default function LoginPage() { + const [username, setUsername] = useState('') + const [password, setPassword] = useState('') + const [error, setError] = useState('') + const [loading, setLoading] = useState(false) + const [googleReady, setGoogleReady] = useState(false) + const router = useRouter() + const googleBtnRef = useRef(null) + + const googleClientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || '' + + const completeLogin = useCallback(async (path: string, body: any) => { + const res = await fetch(path, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }) + + if (!res.ok) { + const data = await res.json().catch(() => ({})) + setError(data.error || 'Login failed') + setLoading(false) + return false + } + + router.push('/') + router.refresh() + return true + }, [router]) + + async function handleSubmit(e: FormEvent) { + e.preventDefault() + setError('') + setLoading(true) + + try { + await completeLogin('/api/auth/login', { username, password }) + } catch { + setError('Network error') + setLoading(false) + } + } + + useEffect(() => { + if (!googleClientId) return + + const onScriptLoad = () => { + if (!window.google || !googleBtnRef.current) return + window.google.accounts.id.initialize({ + client_id: googleClientId, + callback: async (response: any) => { + setError('') + setLoading(true) + try { + const ok = await completeLogin('/api/auth/google', { credential: response?.credential }) + if (!ok) return + } catch { + setError('Google sign-in failed') + setLoading(false) + } + }, + }) + window.google.accounts.id.renderButton(googleBtnRef.current, { + theme: 'outline', + size: 'large', + width: 320, + text: 'signin_with', + shape: 'pill', + }) + setGoogleReady(true) + } + + const existing = document.querySelector('script[data-google-gsi="1"]') as HTMLScriptElement | null + if (existing) { + if (window.google) onScriptLoad() + return + } + + const script = document.createElement('script') + script.src = 'https://accounts.google.com/gsi/client' + script.async = true + script.defer = true + script.setAttribute('data-google-gsi', '1') + script.onload = onScriptLoad + script.onerror = () => setError('Failed to load Google Sign-In') + document.head.appendChild(script) + }, [googleClientId, completeLogin]) + + return ( +
+
+
+
+ MC +
+

Mission Control

+

Sign in to continue

+
+ +
+ {error && ( +
+ {error} +
+ )} + +
+ + setUsername(e.target.value)} + className="w-full h-10 px-3 rounded-lg bg-secondary border border-border text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-smooth" + placeholder="Enter username" + autoComplete="username" + autoFocus + required + /> +
+ +
+ + setPassword(e.target.value)} + className="w-full h-10 px-3 rounded-lg bg-secondary border border-border text-foreground text-sm placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-smooth" + placeholder="Enter password" + autoComplete="current-password" + required + /> +
+ + +
+ +
+
+ or +
+
+ +
+ {googleClientId ? ( +
+ ) : ( +
Google sign-in not configured
+ )} +
+ {googleClientId && !googleReady &&

Loading Google Sign-In...

} + +

OpenClaw Agent Orchestration

+
+
+ ) +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..f35d64cb980d25d5afe254d8915188aca3589fb1 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,184 @@ +'use client' + +import { useEffect, useState } from 'react' +import { NavRail } from '@/components/layout/nav-rail' +import { HeaderBar } from '@/components/layout/header-bar' +import { LiveFeed } from '@/components/layout/live-feed' +import { Dashboard } from '@/components/dashboard/dashboard' +import { AgentSpawnPanel } from '@/components/panels/agent-spawn-panel' +import { LogViewerPanel } from '@/components/panels/log-viewer-panel' +import { CronManagementPanel } from '@/components/panels/cron-management-panel' +import { MemoryBrowserPanel } from '@/components/panels/memory-browser-panel' +import { TokenDashboardPanel } from '@/components/panels/token-dashboard-panel' +import { SessionDetailsPanel } from '@/components/panels/session-details-panel' +import { TaskBoardPanel } from '@/components/panels/task-board-panel' +import { ActivityFeedPanel } from '@/components/panels/activity-feed-panel' +import { AgentSquadPanelPhase3 } from '@/components/panels/agent-squad-panel-phase3' +import { AgentCommsPanel } from '@/components/panels/agent-comms-panel' +import { StandupPanel } from '@/components/panels/standup-panel' +import { OrchestrationBar } from '@/components/panels/orchestration-bar' +import { NotificationsPanel } from '@/components/panels/notifications-panel' +import { UserManagementPanel } from '@/components/panels/user-management-panel' +import { AuditTrailPanel } from '@/components/panels/audit-trail-panel' +import { AgentHistoryPanel } from '@/components/panels/agent-history-panel' +import { WebhookPanel } from '@/components/panels/webhook-panel' +import { SettingsPanel } from '@/components/panels/settings-panel' +import { GatewayConfigPanel } from '@/components/panels/gateway-config-panel' +import { IntegrationsPanel } from '@/components/panels/integrations-panel' +import { AlertRulesPanel } from '@/components/panels/alert-rules-panel' +import { MultiGatewayPanel } from '@/components/panels/multi-gateway-panel' +import { SuperAdminPanel } from '@/components/panels/super-admin-panel' +import { ChatPanel } from '@/components/chat/chat-panel' +import { useWebSocket } from '@/lib/websocket' +import { useServerEvents } from '@/lib/use-server-events' +import { useMissionControl } from '@/store' + +export default function Home() { + const { connect } = useWebSocket() + const { activeTab, setCurrentUser, liveFeedOpen, toggleLiveFeed } = useMissionControl() + + // Connect to SSE for real-time local DB events (tasks, agents, chat, etc.) + useServerEvents() + const [isClient, setIsClient] = useState(false) + + useEffect(() => { + setIsClient(true) + + // Fetch current user + fetch('/api/auth/me') + .then(res => res.ok ? res.json() : null) + .then(data => { if (data?.user) setCurrentUser(data.user) }) + .catch(() => {}) + + // Auto-connect to gateway on mount + const wsToken = process.env.NEXT_PUBLIC_GATEWAY_TOKEN || process.env.NEXT_PUBLIC_WS_TOKEN || '' + const explicitWsUrl = process.env.NEXT_PUBLIC_GATEWAY_URL || '' + const gatewayPort = process.env.NEXT_PUBLIC_GATEWAY_PORT || '18789' + const gatewayHost = process.env.NEXT_PUBLIC_GATEWAY_HOST || window.location.hostname + const gatewayProto = + process.env.NEXT_PUBLIC_GATEWAY_PROTOCOL || + (window.location.protocol === 'https:' ? 'wss' : 'ws') + const wsUrl = explicitWsUrl || `${gatewayProto}://${gatewayHost}:${gatewayPort}` + connect(wsUrl, wsToken) + }, [connect, setCurrentUser]) + + if (!isClient) { + return ( +
+
+
+ MC +
+
+
+ Loading Mission Control... +
+
+
+ ) + } + + return ( +
+ {/* Left: Icon rail navigation (hidden on mobile, shown as bottom bar instead) */} + + + {/* Center: Header + Content */} +
+ +
+ +
+
+ + {/* Right: Live feed (hidden on mobile) */} + {liveFeedOpen && ( +
+ +
+ )} + + {/* Floating button to reopen LiveFeed when closed */} + {!liveFeedOpen && ( + + )} + + {/* Chat panel overlay */} + +
+ ) +} + +function ContentRouter({ tab }: { tab: string }) { + switch (tab) { + case 'overview': + return ( + <> + +
+ +
+ + ) + case 'tasks': + return + case 'agents': + return ( + <> + + +
+ +
+ + ) + case 'activity': + return + case 'notifications': + return + case 'standup': + return + case 'spawn': + return + case 'sessions': + return + case 'logs': + return + case 'cron': + return + case 'memory': + return + case 'tokens': + return + case 'users': + return + case 'history': + return + case 'audit': + return + case 'webhooks': + return + case 'alerts': + return + case 'gateways': + return + case 'gateway-config': + return + case 'integrations': + return + case 'settings': + return + case 'super-admin': + return + default: + return + } +} diff --git a/src/components/chat/chat-input.tsx b/src/components/chat/chat-input.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8aba3aff5b43bdf0ba34b0e1b2ccc266d220e2ce --- /dev/null +++ b/src/components/chat/chat-input.tsx @@ -0,0 +1,175 @@ +'use client' + +import { useRef, useEffect, useState, useCallback } from 'react' +import { useMissionControl } from '@/store' + +interface ChatInputProps { + onSend: (content: string) => void + disabled?: boolean + agents?: Array<{ name: string; role: string }> +} + +export function ChatInput({ onSend, disabled, agents = [] }: ChatInputProps) { + const { chatInput, setChatInput, isSendingMessage } = useMissionControl() + const textareaRef = useRef(null) + const [showMentions, setShowMentions] = useState(false) + const [mentionFilter, setMentionFilter] = useState('') + const [mentionIndex, setMentionIndex] = useState(0) + + const filteredAgents = agents.filter(a => + a.name.toLowerCase().includes(mentionFilter.toLowerCase()) + ) + + const autoResize = useCallback(() => { + const textarea = textareaRef.current + if (textarea) { + textarea.style.height = 'auto' + textarea.style.height = Math.min(textarea.scrollHeight, 120) + 'px' + } + }, []) + + useEffect(() => { + autoResize() + }, [chatInput, autoResize]) + + // Focus textarea when panel opens + useEffect(() => { + if (!disabled) { + textareaRef.current?.focus() + } + }, [disabled]) + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (showMentions) { + if (e.key === 'ArrowDown') { + e.preventDefault() + setMentionIndex(i => Math.min(i + 1, filteredAgents.length - 1)) + return + } + if (e.key === 'ArrowUp') { + e.preventDefault() + setMentionIndex(i => Math.max(i - 1, 0)) + return + } + if (e.key === 'Enter' || e.key === 'Tab') { + e.preventDefault() + if (filteredAgents[mentionIndex]) { + insertMention(filteredAgents[mentionIndex].name) + } + return + } + if (e.key === 'Escape') { + setShowMentions(false) + return + } + } + + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault() + handleSend() + } + } + + const handleChange = (e: React.ChangeEvent) => { + const value = e.target.value + setChatInput(value) + + const cursorPos = e.target.selectionStart + const textBeforeCursor = value.slice(0, cursorPos) + const atMatch = textBeforeCursor.match(/@(\w*)$/) + + if (atMatch) { + setMentionFilter(atMatch[1]) + setShowMentions(true) + setMentionIndex(0) + } else { + setShowMentions(false) + } + } + + const insertMention = (agentName: string) => { + const textarea = textareaRef.current + if (!textarea) return + + const cursorPos = textarea.selectionStart + const textBeforeCursor = chatInput.slice(0, cursorPos) + const textAfterCursor = chatInput.slice(cursorPos) + const atIndex = textBeforeCursor.lastIndexOf('@') + + const newText = textBeforeCursor.slice(0, atIndex) + `@${agentName} ` + textAfterCursor + setChatInput(newText) + setShowMentions(false) + + setTimeout(() => { + const newPos = atIndex + agentName.length + 2 + textarea.setSelectionRange(newPos, newPos) + textarea.focus() + }, 0) + } + + const handleSend = () => { + const trimmed = chatInput.trim() + if (!trimmed || disabled || isSendingMessage) return + onSend(trimmed) + setChatInput('') + if (textareaRef.current) { + textareaRef.current.style.height = 'auto' + } + } + + return ( +
+ {/* Mention autocomplete dropdown */} + {showMentions && filteredAgents.length > 0 && ( +
+ {filteredAgents.map((agent, i) => ( + + ))} +
+ )} + +
+