diff --git a/.claude/settings.json b/.claude/settings.json deleted file mode 100644 index 37d2962..0000000 --- a/.claude/settings.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "permissions": { - "allow": [ - "mcp__obsmcp__health_check", - "mcp__obsmcp__get_project_status_snapshot", - "mcp__obsmcp__get_current_task", - "mcp__obsmcp__get_latest_handoff", - "mcp__obsmcp__get_blockers", - "mcp__obsmcp__get_recent_work", - "mcp__obsmcp__get_decisions", - "mcp__obsmcp__session_open", - "mcp__obsmcp__get_active_tasks", - "mcp__obsmcp__get_project_brief", - "mcp__obsmcp__search_notes", - "mcp__obsmcp__get_audit_log", - "mcp__obsmcp__list_projects", - "mcp__obsmcp__web_search", - "mcp__obsmcp__get_token_usage_stats", - "mcp__obsmcp__resolve_active_project" - ] - } -} diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e48bddd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +.github +.venv +frontend/node_modules +frontend/dist +server/obsmcp_server/frontend_dist +**/__pycache__ +**/*.pyc +.obsmcp +*.db +.env diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e98d34f --- /dev/null +++ b/.env.example @@ -0,0 +1,9 @@ +# OBSMCP server configuration +# Leave OBSMCP_API_TOKEN blank to disable auth (local mode). +OBSMCP_API_TOKEN= +OBSMCP_DB_PATH=~/.obsmcp/data/obsmcp.db +OBSMCP_HOST=0.0.0.0 +OBSMCP_PORT=8000 + +# Optional: Anthropic API key for semantic descriptions +ANTHROPIC_API_KEY= diff --git a/.gitignore b/.gitignore index 3f1657b..619313b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,22 +1,49 @@ -.venv/ +# Python __pycache__/ -*.pyc -*.pyo +*.py[cod] +*.egg-info/ +.eggs/ +.venv/ +venv/ +env/ +.pytest_cache/ +.ruff_cache/ +.mypy_cache/ +.coverage +htmlcov/ +dist/ +build/ +*.egg + +# Node / frontend +node_modules/ +frontend/dist/ +server/obsmcp_server/frontend_dist/ +.npm/ +.pnpm-store/ +.vite/ + +# OBSMCP local data / configs (never commit!) +.obsmcp/ +*.db +*.db-journal +*.sqlite *.sqlite3 -*.sqlite3-shm -*.sqlite3-wal +obsmcp.db* + +# IDE / OS +.vscode/ +.idea/ +.DS_Store +Thumbs.db +*.swp +*.swo + +# Env / secrets +.env +.env.* +!.env.example + +# Logs *.log -*.zip -/backups/ -/.context/ -/.pytest_cache/ -/.tmp-tests/ -/.tmp-caveman/ -/data/ -/hub/ -/logs/ -/.obsmcp-link.json -/obsidian/ -/projects/ -/registry/ -/workspace/ +logs/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..c78db0c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.5.6 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index b9c9f40..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,41 +0,0 @@ -# AGENTS.md - -You are operating in a shared multi-model continuity system backed by `obsmcp`. - -Before doing substantive work, read in this order: - -1. `.context/PROJECT_CONTEXT.md` -2. `.context/CURRENT_TASK.json` -3. `.context/HANDOFF.md` -4. `.context/DECISIONS.md` -5. `.context/BLOCKERS.json` -6. `.context/RELEVANT_FILES.json` -7. `.context/SESSION_SUMMARY.md` -8. Check if a **Code Atlas** exists for this project. Call `get_code_atlas_status()` via MCP or run `ctx.bat atlas status` from the shell. If the atlas does not exist, call `scan_codebase()`. For large or first-time scans, `scan_codebase()` may return a background job instead of a finished atlas; poll `get_scan_job()` or `wait_for_scan_job()` until it completes. The Code Atlas documents every file, function, class, feature, and cross-reference in the project — it gives you a complete structural understanding without reading every source file. -9. For quick orientation: call `get_audit_log(limit=10)` to see recent activity. -10. For sprint planning: use `bulk_task_ops` to batch-create or batch-update tasks. -11. For token-efficient context: use `generate_compact_context_v2(max_tokens=3000)` — includes decision chains, dependency map, session info, and recommended semantic lookups. -12. For low-latency startup or resumed work, use the cached tiered context and delta tools first: - - `generate_context_profile(profile="fast"|"balanced"|"deep"|"handoff"|"recovery")` - - `generate_delta_context(...)` -13. When you need targeted understanding instead of rereading large files, use semantic tools first: - - `describe_module(module_path)` - - `describe_symbol(symbol_name, module_path?)` - - `describe_feature(feature_name)` - - `search_code_knowledge(query)` - - `get_symbol_candidates(symbol_name)` if a symbol name is ambiguous - - `get_related_symbols(entity_key)` to expand from one symbol to its neighbors -14. For dependency overview: use `get_blocked_tasks()` and `validate_dependencies()` to check task readiness. - -Rules: - -- Do not assume you are the first or only agent. -- Continue the current task instead of restarting discovery. -- Preserve continuity notes, blockers, and decisions. -- When you finish a meaningful chunk, log work to `obsmcp`. -- Before you stop, create a handoff for the next model or tool. - -Preferred write paths: - -- MCP tools if available -- `ctx.bat` if MCP is not available diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index bd5d3d2..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,20 +0,0 @@ -# CLAUDE.md - -This workspace uses `obsmcp` as the shared continuity layer. - -Start here: - -1. Read `.context/PROJECT_CONTEXT.md` -2. Read `.context/CURRENT_TASK.json` -3. Read `.context/HANDOFF.md` -4. Read `.context/DECISIONS.md` -5. Read `.context/BLOCKERS.json` - -Operating rules: - -- Continue the existing project state instead of re-deriving it. -- Treat `.context` as the minimum required continuity package. -- Use `ctx.bat` to log work, create handoffs, and sync files when direct MCP access is missing. -- Record decisions and blockers explicitly. -- Leave a new handoff before ending your turn. - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b9777b4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,52 @@ +# Contributing + +Thanks for helping improve OBSMCP! + +## Local setup + +```bash +python -m pip install -e ".[dev]" +pre-commit install + +cd frontend && npm install +``` + +## Running the stack locally + +```bash +# Terminal 1 — backend +OBSMCP_API_TOKEN=dev obsmcp-server + +# Terminal 2 — frontend +cd frontend +npm run dev # http://localhost:5173 (proxies /api and /ws to 8000) + +# Terminal 3 — local agent (writes to the running backend) +./start.sh # first run prompts for config +``` + +## Code style + +- **Python:** formatted + linted by `ruff` (see `pyproject.toml`). 100-char lines. +- **TypeScript:** strict mode on; lint with `npm run lint`. +- **No ORMs.** Stick to raw `sqlite3`. +- **Every mutation must emit an SSE event** via `broadcast_event(...)`. See existing routers. + +## Adding a new entity + +1. Extend `server/obsmcp_server/schema.sql`. +2. Add a router under `server/obsmcp_server/routers/`, hook it up in `main.py`, and emit events. +3. Add a TypeScript type in `frontend/src/api/types.ts`. +4. Map event types → query keys in `frontend/src/events/EventBus.ts`. +5. Add a page under `frontend/src/pages/`, wire it into `App.tsx` and the sidebar nav. +6. Mirror the write path in `tool/obsmcp/client/http_client.py` so agents can use it offline-first. +7. Add tests in `server/tests/` and `tool/tests/`. + +## Tests + +```bash +pytest -q # backend + tool +cd frontend && npm run typecheck && npm run build +``` + +Run all three locally before opening a PR — this project intentionally has no CI/GitHub Actions. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4f4821e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +# syntax=docker/dockerfile:1.6 + +# ---------- frontend build ---------- +FROM node:20-alpine AS frontend +WORKDIR /app/frontend +COPY frontend/package.json frontend/package-lock.json* ./ +RUN npm install --no-audit --no-fund +COPY frontend . +RUN npm run build + +# ---------- backend image ---------- +FROM python:3.12-slim AS backend +WORKDIR /app + +ENV PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 \ + PIP_NO_CACHE_DIR=1 + +RUN apt-get update && apt-get install -y --no-install-recommends \ + build-essential git curl && \ + rm -rf /var/lib/apt/lists/* + +COPY pyproject.toml README.md ./ +COPY tool tool +COPY server server +# Bring the built frontend into the package's static dir +COPY --from=frontend /app/server/obsmcp_server/frontend_dist server/obsmcp_server/frontend_dist + +RUN pip install --upgrade pip && pip install . + +ENV OBSMCP_HOST=0.0.0.0 \ + OBSMCP_PORT=8000 + +EXPOSE 8000 +CMD ["obsmcp-server"] diff --git a/LICENSE b/LICENSE index 0ae1787..245b239 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 nikzdevz +Copyright (c) 2026 OBSMCP Contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b17ed87..caf55cf 100644 --- a/README.md +++ b/README.md @@ -1,777 +1,166 @@ -# obsmcp - -

- OBS MCP / Obsidian MCP for serious project continuity, multi-session AI work, and developer-grade context engineering. -

+# OBSMCP — Observable Machine Code Protocol -

- License - Tools - Windows First - Transport - Backend - Storage - Continuity - Repo -

+![Python](https://img.shields.io/badge/python-3.12%2B-blue) +![License](https://img.shields.io/badge/license-MIT-green) -

- obsmcp overview -

+OBSMCP is a three-tier observability system for autonomous coding agents: -`obsmcp` stands for `Obsidian MCP`. +1. **Local MCP tool** — runs on the developer's machine, watches the project, streams tasks/sessions/blockers/decisions/work logs/metrics/knowledge-graph edges into a local SQLite database and optionally a remote backend. +2. **FastAPI backend** — raw-SQL SQLite store with full CRUD for every entity, Server-Sent Events bus for real-time mutations, WebSocket mirror, Bearer-token auth. +3. **React dashboard** — TanStack-Query-driven SPA with 10 pages (Dashboard, Tasks, Sessions, Blockers, Decisions, Work Logs, Code Atlas, Knowledge Graph, Performance Logs, Settings), live-invalidated from the SSE event bus. -It is a local-first MCP server and continuity control plane that helps AI coding tools keep working memory between sessions, models, IDEs, and interruptions without turning your chat history into the only source of truth. - -Instead of relying on one long conversation, `obsmcp` stores: - -- project state in SQLite -- compact, prompt-friendly continuity files in `.context` -- human-readable notes and handoffs in Obsidian vaults -- auditable session history, task history, and model-to-model handoffs -- code-aware semantic knowledge through a Code Atlas and semantic lookup layer - -If you are building with Codex, Claude Code, Cursor, Warp, VS Code MCP clients, or your own internal tooling, `obsmcp` is designed to be the shared memory and project-management layer those tools can all use together. - -## At A Glance - -| For developers who need... | obsmcp gives you... | -| --- | --- | -| reliable restart after interrupted AI work | session tracking, resume packets, startup preflight, stale-session detection | -| less prompt replay | fast/balanced/deep/delta/retrieval context surfaces | -| better project memory | tasks, blockers, decisions, handoffs, relevant files, Obsidian notes | -| code-aware context | Code Atlas, semantic search, symbol descriptions, related symbol lookup | -| cost discipline | token-aware context assembly, output policy controls, output compaction, token usage stats | -| multi-client workflows | one project workspace for Codex, Claude Code, Cursor, Warp, and custom MCP clients | - -## Table Of Contents - -- [What Is OBS MCP?](#what-is-obs-mcp) -- [Why It Exists](#why-it-exists) -- [What Makes It Powerful](#what-makes-it-powerful) -- [Architecture At A Glance](#architecture-at-a-glance) -- [Feature Inventory](#feature-inventory) -- [How Token Saving Works](#how-token-saving-works) -- [Installation](#installation) -- [Quick Start](#quick-start) -- [MCP Tool Catalog](#mcp-tool-catalog) -- [Comparison With Other MCP Servers](#comparison-with-other-mcp-servers) -- [Where obsmcp Wins](#where-obsmcp-wins) -- [Where obsmcp Is Weaker](#where-obsmcp-is-weaker) -- [Next Recommended Commit](#next-recommended-commit) -- [Documentation Index](#documentation-index) - -## What Is OBS MCP? - -`obsmcp` is not just another MCP tool server. - -It is a project operations layer for AI-assisted development: - -- it knows what project is active -- it knows what task is current -- it knows what was done recently -- it knows what is blocked -- it knows what should happen next -- it knows which files matter -- it knows when a session is stale or abandoned -- it can produce compact startup context instead of replaying long history - -You can think of it as a hybrid of: - -- a continuity server -- a local project memory system -- a task and handoff tracker -- a prompt-context engineering layer -- a semantic code knowledge service -- an MCP gateway for selected external tools - -## Why It Exists - -Most MCP servers are excellent at one narrow job: - -- file access -- browser automation -- GitHub automation -- memory search -- code execution - -Those are useful, but they do not solve the bigger operational problem: - -> When an AI model stops, switches, crashes, resumes, or hands off work, how does the next model continue the project cleanly? - -That is the problem `obsmcp` is built to solve. - -It gives you: - -- project-scoped memory instead of chat-scoped memory -- structured tasks, blockers, and decisions instead of loose notes -- safe startup checks before a model resumes the wrong thing -- resumable sessions with labels and workstreams -- token-aware context surfaces for fast restart -- human-readable notes for debugging and handoff review - -## What Makes It Powerful - -### 1. Continuity is attached to the project, not to one chat - -`obsmcp` keeps state in a centralized workspace per project, so the next model or IDE can continue from the actual project state. - -### 2. It supports real project management - -It tracks: - -- tasks -- current task -- blockers -- decisions -- work logs -- handoffs -- sessions -- dependencies -- recovery state - -### 3. It is built for multi-session, multi-model work - -With session labels, workstreams, preflight checks, resume boards, and mismatch guards, `obsmcp` is designed for interrupted and branchy work instead of one perfect uninterrupted run. - -### 4. It is optimized for token efficiency - -It does not only store memory. It helps shape what the next model sees: - -- fast context -- balanced context -- deep context -- handoff context -- recovery context -- delta context -- prompt segments -- retrieval context -- raw-output compaction -- output-response policy controls - -### 5. It is code-aware - -The Code Atlas + semantic layer means tools can ask: - -- what this module does -- what this function does -- what features exist -- which symbols are related -- what changed since the last handoff - -That is very different from a plain memory bank. - -## Architecture At A Glance - -```text -Developer / IDE / AI Client - | - v - MCP / CLI / File Reads - | - v - obsmcp - | - +--> SQLite project state - +--> .context continuity files - +--> per-project Obsidian vault - +--> session folders and handoffs - +--> semantic code atlas - +--> optional provider-backed tools ``` - -### Core layers - -| Layer | Purpose | -| --- | --- | -| SQLite | System of record for tasks, sessions, blockers, decisions, logs, handoffs, metrics | -| `.context` | Universal fallback surface for tools that cannot call MCP directly | -| Obsidian vault | Human-readable operational memory and project notes | -| Session folders | Durable artifacts like metadata, heartbeat history, worklog, and handoff files | -| Code Atlas | File/function/class/feature understanding across the repository | -| MCP server | Structured tool access over HTTP | -| `ctx.bat` CLI | Shell fallback when MCP integration is unavailable or inconvenient | - -### Workspace model - -Each project gets its own centralized workspace under: - -```text -projects// +┌────────────────────────┐ local SQLite (always) +│ Local MCP Tool (you) │──┬────────────────┐ +│ • monitors • scanners│ │ ↕ cloud sync (optional) +└────────┬───────────────┘ │ │ + │ stdio │ ▼ + ▼ │ ┌──────────────────┐ + Claude / Cursor / CLI │ │ FastAPI backend │ ←→ React SPA + │ │ SSE / WS bus │ + └─────►│ SQLite store │ + └──────────────────┘ ``` -With subdirectories such as: - -- `data/db/` -- `.context/` -- `vault/` -- `sessions/` -- `logs/` - -## Feature Inventory - -### Project continuity - -- centralized project workspace per repo -- project registration and routing -- repo bridge attachment for path inference -- current-task tracking -- relevant-file tracking -- model-to-model handoffs -- daily note stream -- audit trail - -### Session management - -- auditable session open / heartbeat / close lifecycle -- session labels for human-readable tracking -- stable workstream keys for related sessions -- startup preflight warnings -- startup resume board -- session mismatch guard for unsafe auto-resume -- stale-session and abandoned-session detection -- emergency recovery handoffs -- session lineage - -### Context engineering - -- compact context -- token-budget-aware compact context v2 -- tiered profiles: `fast`, `balanced`, `deep`, `handoff`, `recovery` -- delta context since handoff/session/timestamp -- retrieval context -- startup context -- prompt segments for cache-friendly assembly -- progressive chunked context loading - -### Token and output optimization - -- token usage metrics -- raw tool-output capture -- noisy command-output compaction -- output-response policy -- operation-aware optimization policy -- fast-path deterministic responses - -### Code understanding - -- full codebase scan / Code Atlas -- semantic module descriptions -- semantic symbol descriptions -- feature descriptions -- related-symbol expansion -- semantic search -- background scan jobs - -### Developer operations - -- command-event recording and replay -- command risk classification -- task templates -- bulk task operations -- dependency management -- log retention / expiry -- project export - -### External tool gateway +## Operating modes -- web search -- image understanding +- **Standalone** (default) — everything lives in `~/.obsmcp/data/obsmcp.db`. The dashboard is served at by the bundled FastAPI server. +- **Cloud sync** — same local DB + every write is mirrored to a remote backend. The agent stays fully functional offline; writes flush to the server automatically when connectivity returns. -## How Token Saving Works +## Quickstart -This is one of the biggest differences between `obsmcp` and many memory-oriented MCP servers. +### 1. Install the Python tool -`obsmcp` tries to save tokens at multiple levels: - -### Input-token savings - -- use `generate_fast_context` or `generate_context_profile("fast")` for minimal startup context -- use `generate_delta_context` to send only what changed instead of replaying full history -- use `generate_retrieval_context` for targeted context instead of large note dumps -- use semantic lookups instead of rereading giant files -- use prompt segments for cache-friendly context assembly - -### Output-token savings - -- `compact_tool_output` -- `compact_response` -- `get_output_response_policy` -- `generate_startup_prompt_template` -- gateway-enforced response style on the surfaces `obsmcp` actually controls - -### What `obsmcp` does better than basic memory servers - -Many memory servers reduce repeated context by storing facts. `obsmcp` does that kind of work too, but it also helps decide: - -- what to show now -- how much to show -- which context tier to use -- how to avoid replaying unchanged state -- how to compress verbose tool output safely - -### What it does not claim - -`obsmcp` does not magically reduce every token in every client. Output savings only happen on the surfaces it controls directly. If a client ignores the optimized context or bypasses its response policies, those savings can be reduced. - -## Installation - -## Prerequisites - -- Windows -- Python `3.11+` -- PowerShell or Command Prompt -- Obsidian installed locally if you want live vault-based workflows - -## Recommended install path - -```text -C:\obsmcp +```bash +python -m pip install -e ".[dev]" # clones & editable install +# Optional: LLM-powered semantic descriptions +python -m pip install -e ".[llm]" ``` -This keeps the batch scripts and Task Scheduler paths simple. +### 2. First-run setup -## Install +Windows: -```bat -git clone https://github.com//obsmcp.git C:\obsmcp -cd /d C:\obsmcp -bootstrap_obsmcp.bat ``` - -What `bootstrap_obsmcp.bat` does: - -- creates `.venv` -- upgrades `pip` -- installs Python dependencies from `requirements.txt` - -## Start the server - -```bat -start_obsmcp.bat +start.bat ``` -The server starts locally on: +macOS / Linux: -```text -http://127.0.0.1:9300 +``` +./start.sh ``` -## Stop the server +You'll be prompted for: -```bat -stop_obsmcp.bat -``` +- **Project path** (required) — path to the codebase you want to observe. +- **Backend URL** (optional) — leave blank to stay standalone. +- **API token** (optional) — required if the backend has `OBSMCP_API_TOKEN` set. -## Verify health +Config lands at `~/.obsmcp/config.json` (`%USERPROFILE%\.obsmcp\config.json` on Windows). -```bat -curl http://127.0.0.1:9300/healthz -netstat -ano | findstr :9300 -ctx.bat project list -``` +### 3. Run -## Optional local API token +`start.bat` / `start.sh` launches `python -m obsmcp`, which spins up: -```bat -set OBSMCP_API_TOKEN=your-local-token -``` +- Session monitor + heartbeat +- File watcher (via `watchfiles`, optional) +- Git commit/branch monitor +- Performance monitor (CPU / memory / disk via `psutil`) +- Code Atlas scanner (regex-based multi-language metadata) +- Knowledge graph node extractor + edge builder +- (Standalone only) Local FastAPI dashboard at + +### 4. Plug into an MCP client -## MCP client configuration +Add a stdio server entry to Claude Desktop / Cursor / Claude Code: -```json +```jsonc { "mcpServers": { "obsmcp": { - "transport": "http", - "url": "http://127.0.0.1:9300/mcp" + "command": "python", + "args": ["-m", "obsmcp", "--mcp-stdio"] } } } ``` -## Quick Start - -### 1. Register a project - -```bat -ctx.bat project register --repo D:\Work\MyApp --name "My App" -``` +Tool names exposed: `get_tasks`, `create_task`, `update_task`, `delete_task`, `log_blocker`, `resolve_blocker`, `log_decision`, `log_work`, `start_session`, `end_session`, `scan_codebase`, `get_scan_status`, `add_node`, `add_edge`, `query_graph`, `get_performance_summary`, `sync_state`. -### 2. Create a task +## Cloud deployment -```bat -ctx.bat --project D:\Work\MyApp task create "Bootstrap obsmcp" --description "Initialize continuity for this repo" +```bash +# Build everything (frontend → /server/obsmcp_server/frontend_dist, backend image) +docker compose up --build ``` -### 3. Mark it current +Environment variables: -```bat -ctx.bat --project D:\Work\MyApp start TASK-REPLACE-ME -``` +| Variable | Default | Notes | +|---------------------|------------------------------|------------------------------------------------| +| `OBSMCP_API_TOKEN` | (unset → no auth) | Required for cloud mode | +| `OBSMCP_DB_PATH` | `~/.obsmcp/data/obsmcp.db` | In Docker: `/data/obsmcp.db` (volume-mounted) | +| `OBSMCP_HOST` | `0.0.0.0` | | +| `OBSMCP_PORT` | `8000` | | +| `ANTHROPIC_API_KEY` | (unset) | Enables LLM semantic descriptions (opt-in) | -### 4. Run startup safety checks +## API surface -```bat -ctx.bat --project D:\Work\MyApp preflight --actor codex --initial-request "Continue implementation" --goal "Complete the feature safely" -ctx.bat --project D:\Work\MyApp resume-board -``` +Bearer-token auth on everything under `/api/*` when `OBSMCP_API_TOKEN` is set. See [`server/obsmcp_server/routers/`](server/obsmcp_server/routers/) for the exhaustive list. Highlights: -### 5. Open a named session - -```bat -ctx.bat session open ^ - --actor codex ^ - --client vscode-codex ^ - --model gpt-5 ^ - --project-path D:\Work\MyApp ^ - --task TASK-REPLACE-ME ^ - --label "Managing Director Email" ^ - --workstream managing-director-email ^ - --initial-request "This task is for the managing director's email." ^ - --goal "Draft and finalize the email" -``` +- `GET /api/stats` — counts for every entity (powers the dashboard cards). +- `GET /api/events` — SSE stream of every mutation. +- `WS /ws/dashboard` — WebSocket mirror of the same bus. +- `GET /healthz`, `/readyz`, `/runtime-discovery`, `/mode` — public. -### 6. Log work as you go +All mutations emit a typed SSE event (`task_created`, `blocker_resolved`, `scan_completed`, …). The React client maps event → TanStack Query key and invalidates automatically; no polling. -```bat -ctx.bat --project D:\Work\MyApp log "Drafted the first version" --task TASK-REPLACE-ME --files README.md -``` +## Development -### 7. Close with a handoff +```bash +# Backend +pytest # unit tests (FastAPI + TestClient against tmp SQLite) +ruff check . # lint +mypy # types (best-effort) -```bat -ctx.bat handoff --summary "Draft is complete" --next-steps "Review tone and finalize" --to "next-agent" -ctx.bat session close SESSION-REPLACE-ME --actor codex --summary "Closed cleanly with handoff." +# Frontend +cd frontend +npm install +npm run dev # http://localhost:5173 (proxies /api to :8000) +npm run build # emits to server/obsmcp_server/frontend_dist +npm run typecheck ``` -## MCP Tool Catalog - -`obsmcp` currently exposes `117` MCP tools. - -This is a deliberately broad surface because `obsmcp` is not only a memory tool. It is a continuity, context, code-understanding, and workflow-management server. - -
-Project & Workspace - -- `register_project`: Register a repo with obsmcp and create its centralized workspace. -- `list_projects`: List registered obsmcp projects. -- `resolve_project`: Resolve a project by slug or repo path. -- `resolve_active_project`: Resolve the active project from IDE metadata such as cwd, active file, workspace folders, open files, session_id, task_id, repo_path, or environment hints. Use this before the first continuity write from a plugin or IDE client. -- `get_project_workspace_paths`: Return the workspace paths for a project. -- `attach_repo_bridge`: Write a lightweight bridge file into the repo that points at the centralized obsmcp workspace. -- `migrate_project_layout`: Copy legacy repo-local `.context` and `obsidian/vault` content into the centralized project workspace and attach a repo bridge. -- `sync_hub`: Refresh the central obsmcp hub vault from the registry. -- `health_check`: Return health information about obsmcp. -- `get_server_capabilities`: Return server API/schema versions and supported workflow-safety capabilities. -- `check_client_compatibility`: Compare client API/tool-schema expectations with the current server. -- `list_tools`: Return the obsmcp tool catalog. -- `list_resources`: Return the obsmcp resource catalog. -- `export_project`: Export full project state as JSON (gzipped) and/or Markdown bundle. Creates a timestamped export in `data/exports/`. -- `get_or_create_project`: Auto-detect or create a project from a path hint, session, task, or environment. Resolves from multiple sources and optionally registers if not known. Returns project type metadata, workspace type, and nearby projects. - -
- -
-Project Memory & Notes - -- `get_project_brief`: Return the current project brief sections. -- `get_current_task`: Return the current task. -- `get_active_tasks`: Return open, in-progress, and blocked tasks. -- `get_latest_handoff`: Return the latest handoff. -- `get_recent_work`: Return recent work logs with cursor-style `limit` and `after_id` parameters. -- `get_decisions`: Return recent decisions with cursor-style `limit` and `after_id` parameters. -- `get_blockers`: Return open blockers with cursor-based pagination. -- `get_relevant_files`: Return relevant file paths for a task or the current task. -- `get_table_schema`: Return the SQLite schema for a given table. -- `search_notes`: Search the Obsidian vault for notes. -- `read_note`: Read a note from the Obsidian vault. -- `get_project_status_snapshot`: Return a compact project status snapshot. - -
- -
-Tasks, Decisions & Daily Ops - -- `log_work`: Append a work log entry. -- `log_checkpoint`: Record a completed checkpoint or subtask for a task. -- `update_task`: Update an existing task. -- `create_task`: Create a task. -- `get_task_progress`: Return checkpoint progress and recent checkpoints for a task. -- `log_decision`: Record an ADR-style decision. -- `log_blocker`: Record a blocker. -- `resolve_blocker`: Resolve an open blocker. -- `create_handoff`: Create a model-to-model or user-to-model handoff. -- `append_handoff_note`: Append an additional note to an existing handoff. -- `update_project_brief_section`: Update a named project brief section. -- `create_daily_note_entry`: Append an entry to the daily note stream. -- `set_current_task`: Set the current active task. -- `get_task_templates`: List all available task templates. -- `get_task_template`: Get a specific task template by name. -- `create_task_template`: Create a new task template. -- `delete_task_template`: Delete a task template by name. -- `create_task_from_template`: Create a task from a named template, filling in template variables. -- `quick_log`: One-liner work log that auto-tags the current task. No `task_id` required. -- `get_audit_log`: Full project-wide activity timeline with cursor-based pagination. -- `reset_project`: Wipe project data by scope with audit tracking. -- `bulk_task_ops`: Execute multiple task operations atomically. - -
- -
-Sessions, Startup & Recovery - -- `session_open`: Open an auditable AI session with heartbeat and write-back policy. -- `session_heartbeat`: Record a session heartbeat and optionally emit a heartbeat work log. -- `session_close`: Close a session with summary and optional handoff creation. -- `get_active_sessions`: List open tracked sessions with cursor-based pagination. -- `detect_missing_writeback`: Audit sessions for missing write-back, missing handoffs, or overdue heartbeats. -- `get_startup_preflight`: Run startup safety checks before opening or resuming a session. -- `get_resume_board`: Return a startup dashboard of open tasks, paused tasks, stale sessions, latest handoffs, and the recommended resume target. -- `generate_resume_packet`: Generate a compact resume packet for the next tool or model and write it to the project workspace. -- `generate_emergency_handoff`: Generate a best-effort handoff from persisted state when a session ended abruptly. -- `recover_session`: Recover an interrupted session by generating an emergency handoff and resume packet. -- `session_replay`: Reconstruct the timeline of events within a session. -- `generate_cross_tool_handoff`: Generate a structured JSON handoff payload for another tool or IDE. -- `get_session_lineage_chain`: Traverse parent/child session lineage. -- `set_session_environment`: Attach IDE/environment metadata to an active session. - -
- -
-Context Engineering & Token Efficiency - -- `sync_context_files`: Force a sync of generated context and Obsidian files. -- `generate_compact_context`: Generate compact context for manual prompt injection. -- `generate_compact_context_v2`: Token-budget-aware compact context with decision chains, dependency map, session info, and smart truncation. -- `generate_context_profile`: Generate a cached tiered context profile such as `fast`, `balanced`, `deep`, `handoff`, or `recovery`. -- `generate_delta_context`: Generate a compact delta view showing what changed since a handoff, session, or timestamp. -- `generate_prompt_segments`: Generate stable and dynamic prompt segments for cache-friendly context assembly. -- `generate_retrieval_context`: Generate retrieval-first context with ranked files, recent work, decisions, blockers, and semantic hits for a query. -- `generate_task_snapshot`: Generate a detailed snapshot for a task. -- `record_token_usage`: Record provider or local token usage metrics, including prompt cache fields and compaction savings. -- `get_token_usage_stats`: Return recent token, compaction, and prompt-cache usage aggregates for the project. -- `get_output_response_policy`: Resolve the effective output-token policy for the current task/operation. -- `compact_tool_output`: Compact noisy tool output and optionally save full raw output for debugging. -- `compact_response`: Compress verbose text output while preserving code blocks, URLs, file paths, and errors. -- `get_raw_output_capture`: Retrieve metadata or full content for a saved raw output capture. -- `get_fast_path_response`: Return a deterministic no-LLM fast-path response for common startup and status needs. -- `get_optimization_policy`: Return the active adaptive optimization policy for a mode, task, command, and exit state. -- `list_context_chunks`: List prioritized chunk metadata for a context artifact. -- `generate_progressive_context`: Render one or more prioritized chunks from a context artifact. -- `generate_startup_context`: Generate a delta-first startup context with fast baseline, recent command history, and execution hints. -- `generate_startup_prompt_template`: Return the first-contact startup prompt template for tools and agents. -- `generate_fast_context`: Generate a guaranteed-fast L0-only context for startup/resume use cases. -- `retrieve_context_chunk`: Retrieve a specific chunk of a context artifact for large profile navigation. - -
- -
-Command Intelligence - -- `record_command_event`: Record a terminal command outcome with compact summaries and optional raw output capture. -- `record_command_batch`: Record a batch of command outcomes and return an aggregate summary with risk counts. -- `get_command_event`: Retrieve a recorded command event by ID. -- `get_recent_commands`: List recent recorded command events with cursor-based pagination. -- `get_last_command_result`: Return the most recent recorded command event for a session or task. -- `get_command_failures`: List recent failing command events for a session or task. -- `get_command_execution_policy`: Classify a command for batching and review risk. - -
- -
-Code Atlas & Semantic Knowledge - -- `scan_codebase`: Scan the project directory and generate a Code Atlas documenting every file, function, class, and feature. -- `get_code_atlas_status`: Return current atlas status without regenerating it. -- `start_scan_job`: Queue a background Code Atlas scan job. -- `get_scan_job`: Get the current status and result payload for a background scan job. -- `list_scan_jobs`: List recent background scan jobs for the project. -- `wait_for_scan_job`: Poll a background scan job until it completes or times out. -- `describe_module`: Return a cached or freshly generated semantic description for a module/file. -- `describe_symbol`: Return a semantic description for a function or class. -- `describe_feature`: Return a semantic description for a feature tag from the Code Atlas. -- `search_code_knowledge`: Search semantic knowledge and symbol index entries. -- `get_symbol_candidates`: Return matching function/class symbol candidates for a name. -- `get_related_symbols`: Return nearby or feature-related symbols for a semantic entity. -- `invalidate_semantic_cache`: Mark semantic description cache entries stale by entity or file. -- `refresh_semantic_description`: Force a fresh semantic description generation for an entity lookup. - -
- -
-Dependencies & Retention - -- `configure_log_expiry`: Set the work log retention period in days. -- `expire_old_logs`: Purge work logs older than the configured retention period. -- `get_log_stats`: Return work log statistics and current expiry settings. -- `add_task_dependency`: Link a task as blocked by other tasks and/or blocking other tasks. -- `remove_task_dependency`: Remove task dependencies. -- `get_task_dependency`: Get dependencies for a specific task. -- `get_all_dependencies`: Get all task dependencies across the project. -- `get_blocked_tasks`: Return tasks currently blocked by unresolved dependencies. -- `validate_dependencies`: Validate all task dependencies. - -
- -
-External / Provider Tools - -- `web_search`: Run a web search through obsmcp using the configured provider. -- `understand_image`: Analyze an image through obsmcp using the configured provider. - -
- -## Comparison With Other MCP Servers - -This section is intentionally practical and honest. - -Not all MCP servers solve the same problem, so this is not a strict "winner takes all" comparison. - -`obsmcp` is strongest when you care about continuity, restart safety, project memory, and developer operations. - -It is not automatically the best choice when you only need one narrow capability like browser control or GitHub automation. - -### Comparison matrix - -| Server / category | What it is best at | Where it wins | Where `obsmcp` wins | Where `obsmcp` is weaker | -| --- | --- | --- | --- | --- | -| **Caveman / DIY MCP stack** | Minimal custom setup, hand-rolled memory, quick experiments | Lowest conceptual overhead, easiest to customize quickly | Structured continuity, task/handoff/session management, token-aware startup, auditability, semantic knowledge | `obsmcp` is heavier and more opinionated than a tiny one-file or prompt-only setup | -| **[Context Portal / ConPort](https://github.com/GreatScottyMac/context-portal)** | Project-specific memory bank and RAG backend | Strong structured project memory, SQLite workspace, knowledge graph, semantic search | Stronger session lifecycle, handoffs, startup safety rails, resume board, output/token engineering, command intelligence | ConPort is more narrowly focused on memory-bank workflows and may feel simpler if that is all you need | -| **[Mem0 / OpenMemory MCP](https://github.com/mem0ai/mem0)** | Long-term agent memory and retrieval | Strong memory-centric positioning, retrieval focus, secure/local memory story | Better project operations, richer handoffs, explicit current-task/task dependency model, audit trail, code atlas, session recovery | Mem0 is more specialized if your main goal is reusable memory across many assistants rather than project execution workflow | -| **[Claude-Flow / RuFlow ecosystem](https://github.com/ruvnet/ruflo)** | Multi-agent orchestration and swarm-style automation | Agent orchestration, large tool surface, automation-heavy workflows | Simpler local continuity model, cleaner project-state tracking, more explicit handoffs and restart safety, lower operational sprawl for solo/small-team dev work | `obsmcp` is not a swarm/orchestration platform and does less around multi-agent hive execution | -| **[GitHub MCP Server](https://docs.github.com/en/copilot/how-tos/provide-context/use-mcp/use-the-github-mcp-server)** | GitHub-native repository, issue, PR, and workflow operations | Best when the task is "work with GitHub itself" | Better persistent local continuity, local task/project memory, handoff discipline, codebase restart context | `obsmcp` is not a replacement for deep GitHub API operations | -| **[Playwright MCP](https://github.com/microsoft/playwright-mcp)** | Browser automation, testing, and UI interaction | Best-in-class for browser workflows | Better at long-lived project memory, multi-session continuity, local project governance | `obsmcp` does not replace a browser automation specialist | -| **[Model Context Protocol reference servers](https://github.com/modelcontextprotocol/servers)** | Focused single-purpose tools like filesystem, fetch, git, and memory | Simple, composable, narrow tools with low ambiguity | `obsmcp` unifies continuity, startup context, handoffs, sessions, semantic code understanding, and optimization in one system | The reference servers are usually simpler and easier to reason about when you only need one narrow capability | - -### Token-saving comparison - -| Server / category | Token-saving approach | Strengths | Limits | -| --- | --- | --- | --- | -| `obsmcp` | Tiered context profiles, delta context, retrieval context, semantic lookups, command-output compaction, output-response policy, token metrics | Broadest token strategy across both input and selected output surfaces | More moving parts to understand and tune | -| ConPort | Structured project memory, queryable context, vector/RAG support, prompt-caching-friendly structure | Good for memory retrieval over large project memory | Less focused on session startup packets, handoff discipline, and output compaction | -| Mem0 | Memory retrieval instead of full-history replay | Strong long-term memory efficiency story | Not a full project continuity and startup-governance layer | -| Claude-Flow / RuFlow | Orchestration, tool specialization, workflow automation | Can reduce manual prompting through agent specialization | More orchestration overhead; not primarily a continuity/token-governance system | -| GitHub MCP | Tool-level context scoping inside GitHub workflows | Prevents over-fetching when the task is GitHub-specific | Does not solve local repo continuity or multi-session task memory | -| Playwright MCP | Tool use instead of verbose browser transcripts | Efficient for UI execution flows | Not a continuity engine | -| DIY / Caveman | Minimal overhead by doing almost nothing automatically | Low system overhead | Most token discipline must be done manually by the operator | - -### Feature-by-feature perspective for developers - -| Feature | obsmcp | Typical narrow MCP server | -| --- | --- | --- | -| Project-scoped memory | Strong | Usually weak or absent | -| Current task tracking | Native | Usually absent | -| Structured handoffs | Native | Usually absent | -| Resume safety | Strong | Usually manual | -| Session lifecycle | Strong | Often minimal | -| Token-aware startup context | Strong | Often absent | -| Code semantic understanding | Strong | Usually absent unless specialized | -| Browser automation | Weak by itself | Strong in Playwright MCP | -| GitHub automation | Moderate to weak | Strong in GitHub MCP | -| Memory graph / agent memory | Moderate to strong | Strong in memory-specialized servers | -| Operational simplicity | Moderate | Often simpler in narrow servers | -| Auditability | Strong | Varies widely | - -### Important honesty note on "Caveman" and "RuFlow" - -As of April 14, 2026, I could verify a maintained public ecosystem around `ruvnet/ruflo` / Claude-Flow-style orchestration, but I could not verify one single canonical MCP product named `Caveman` in the same way. In this README, `Caveman` is therefore treated as shorthand for a very minimal, DIY, or hand-rolled MCP + prompt-memory approach rather than a verified official comparison target. +Pre-commit hooks (`pre-commit install`) run Ruff + trailing-whitespace fixes. -That distinction matters, because `obsmcp` is strongest when compared against: - -- DIY continuity systems -- memory-bank-only MCP servers -- orchestration-heavy MCP stacks -- narrow specialist MCP servers - -## Where obsmcp Wins - -Choose `obsmcp` when you want: - -- one continuity layer for many clients -- durable task/session/handoff state -- safer restarts after interruptions -- token-aware startup and resume -- explicit blockers, decisions, and relevant files -- semantic code understanding tied to project continuity -- auditable AI work instead of hidden chat-only memory +## Architecture notes -It is especially strong for: - -- long-lived coding projects -- multi-day AI-assisted development -- model switching and handoffs -- teams experimenting with multiple AI clients -- debugging "the model forgot what it was doing" problems -- controlling token costs on large projects - -## Where obsmcp Is Weaker - -Choose another tool, or combine another MCP with `obsmcp`, when you need: - -- first-class browser automation: use Playwright MCP -- heavy GitHub-native workflows: use GitHub MCP Server -- swarm-style multi-agent orchestration: use Claude-Flow / RuFlow -- a simpler memory-bank-only system: use ConPort or Mem0 -- the smallest possible setup with almost zero concepts: use a DIY minimal server - -Current practical cons of `obsmcp`: - -- Windows-first scripts and docs -- broad tool surface can feel large at first -- more state and moving parts than narrow single-purpose servers -- output-token enforcement only applies where `obsmcp` controls generation -- not a replacement for specialist browser or GitHub automation servers -- not a full multi-agent orchestration framework - -## Next Recommended Commit - -

- obsmcp roadmap -

- -The next high-value commit is already scoped in [docs/NEXT_COMMIT_PLAN.md](docs/NEXT_COMMIT_PLAN.md). - -Recommended direction: - -- improve VS Code startup integration so clients automatically use: - - `resolve_active_project` - - `get_startup_preflight` - - `get_resume_board` -- make output-token strategy easier to adopt by surfacing: - - recommended output modes - - task-type presets - - token-savings visibility in dashboards - -Suggested next commit title: - -```text -Improve VS Code startup flow and expose output-token strategy defaults -``` +- **No ORM.** The backend uses `sqlite3` directly via `threading.local()` connections. Columns that hold JSON (`tags`, `metadata`, `imports`, `exports`) are stored as JSON strings and decoded in Python. +- **IDs are UUIDs** minted client-side to allow offline-first writes. +- **Timestamps are ISO-8601 UTC** strings. +- **SSE broadcaster is thread-safe** — any handler can call `broadcast_event()` and it will fan out via `loop.call_soon_threadsafe`. +- **Graceful degradation** — if the SSE stream drops, the React app stays usable and shows an "Offline" indicator in the sidebar. Reconnection is automatic. +- **Static frontend served by the backend** — after `npm run build` the backend mounts `frontend_dist/` at `/` so a single binary serves the full app. -## Documentation Index +## Status / roadmap -- [Next Commit Plan](docs/NEXT_COMMIT_PLAN.md) -- [Architecture](docs/ARCHITECTURE.md) -- [Usage Guide](docs/USAGE.md) -- [Installation Guide](docs/INSTALLATION.md) -- [Folder Structure](docs/FOLDER_STRUCTURE.md) -- [Obsidian Integration](docs/OBSIDIAN.md) -- [Startup Automation](docs/STARTUP.md) -- [Testing](docs/TESTING.md) -- [Troubleshooting](docs/TROUBLESHOOTING.md) +Scaffolded in this PR: -## Bottom Line +- [x] Full CRUD + SSE for every entity in the spec +- [x] Local dual-mode HTTP client (SQLite + background cloud sync) +- [x] MCP stdio tool server exposing 17 tool functions +- [x] 10-page React dashboard with live SSE-driven cache invalidation +- [x] Docker image + `docker compose` deployment +- [x] Ruff + Pytest for the Python side, TypeScript typecheck + Vite build for the frontend, `docker build` for the server image (all runnable locally — no CI configured by design) -If you need a **single-purpose MCP server**, there are excellent specialized options. +Known gaps / follow-ups: -If you need a **project continuity system for real development work** that can: +- [ ] tree-sitter-based language parsing (currently regex heuristics) +- [ ] SQLite backup rotation +- [ ] LLM semantic descriptions are wired but opt-in; no batching/cost controls yet +- [ ] Optional GraphQL endpoint (spec marks as optional) -- remember what is happening -- tell the next model what matters -- survive interruptions -- reduce token waste -- track tasks and handoffs -- understand the codebase +## License -then `obsmcp` is a much stronger foundation than a basic MCP tool wrapper or a purely chat-memory approach. +MIT — see [`LICENSE`](LICENSE). diff --git a/backup_obsmcp.bat b/backup_obsmcp.bat deleted file mode 100644 index 25f859e..0000000 --- a/backup_obsmcp.bat +++ /dev/null @@ -1,10 +0,0 @@ -@echo off -setlocal -cd /d "%~dp0" - -if exist ".venv\Scripts\python.exe" ( - ".venv\Scripts\python.exe" scripts\backup_obsmcp.py -) else ( - py -3 scripts\backup_obsmcp.py -) - diff --git a/bootstrap_obsmcp.bat b/bootstrap_obsmcp.bat deleted file mode 100644 index 4bb2794..0000000 --- a/bootstrap_obsmcp.bat +++ /dev/null @@ -1,13 +0,0 @@ -@echo off -setlocal -cd /d "%~dp0" - -if not exist ".venv\Scripts\python.exe" ( - py -3 -m venv .venv -) - -".venv\Scripts\python.exe" -m pip install --upgrade pip -".venv\Scripts\python.exe" -m pip install -r requirements.txt - -echo obsmcp bootstrap complete. - diff --git a/cli/__init__.py b/cli/__init__.py deleted file mode 100644 index 433e5ca..0000000 --- a/cli/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -"""obsmcp CLI package.""" - diff --git a/cli/main.py b/cli/main.py deleted file mode 100644 index 7382c7c..0000000 --- a/cli/main.py +++ /dev/null @@ -1,805 +0,0 @@ -from __future__ import annotations - -import argparse -import json -from typing import Any - -from server.config import load_config -from server.service import ObsmcpService - - -def _print(value: Any) -> None: - if isinstance(value, str): - print(value) - return - print(json.dumps(value, indent=2, ensure_ascii=True)) - - -def _csv(value: str | None) -> list[str]: - if not value: - return [] - return [item.strip() for item in value.split(",") if item.strip()] - - -def build_parser() -> argparse.ArgumentParser: - parser = argparse.ArgumentParser(prog="ctx", description="obsmcp continuity CLI") - parser.add_argument("--config", default=None, help="Path to obsmcp config JSON.") - parser.add_argument("--project", dest="project_path", default=None, help="Project root path. Defaults to OBSMCP_PROJECT env var or configured default.") - subparsers = parser.add_subparsers(dest="command", required=True) - - start = subparsers.add_parser("start", help="Set the current task and mark it in progress.") - start.add_argument("task_id") - start.add_argument("--actor", default="ctx") - - log = subparsers.add_parser("log", help="Append a work log entry.") - log.add_argument("message") - log.add_argument("--task", dest="task_id") - log.add_argument("--summary") - log.add_argument("--files") - log.add_argument("--actor", default="ctx") - - handoff = subparsers.add_parser("handoff", help="Create a handoff for another model or tool.") - handoff.add_argument("--summary", required=True) - handoff.add_argument("--next-steps", default="") - handoff.add_argument("--open-questions", default="") - handoff.add_argument("--note", default="") - handoff.add_argument("--task", dest="task_id") - handoff.add_argument("--from", dest="from_actor", default="ctx") - handoff.add_argument("--to", dest="to_actor", default="next-agent") - - sync = subparsers.add_parser("sync", help="Regenerate .context and Obsidian files.") - - status = subparsers.add_parser("status", help="Show the project status snapshot.") - preflight = subparsers.add_parser("preflight", help="Run startup safety checks before opening or resuming a session.") - preflight.add_argument("--actor", default="") - preflight.add_argument("--task", dest="task_id") - preflight.add_argument("--session", dest="session_id") - preflight.add_argument("--initial-request", default="") - preflight.add_argument("--goal", dest="session_goal", default="") - preflight.add_argument("--label", dest="session_label", default="") - preflight.add_argument("--workstream", dest="workstream_key", default="") - preflight.add_argument("--client", dest="client_name", default="") - preflight.add_argument("--model", dest="model_name", default="") - resume_board = subparsers.add_parser("resume-board", help="Show the startup resume dashboard.") - compat = subparsers.add_parser("compat", help="Check client/server compatibility.") - compat.add_argument("--client-api-version", default="") - compat.add_argument("--client-tool-schema-version", type=int) - compat.add_argument("--client", dest="client_name", default="") - compat.add_argument("--model", dest="model_name", default="") - - blockers = subparsers.add_parser("blockers", help="Show open blockers.") - - note = subparsers.add_parser("note", help="Append a daily note entry.") - note.add_argument("entry") - note.add_argument("--date", dest="note_date") - note.add_argument("--actor", default="ctx") - - current = subparsers.add_parser("current", help="Show the current task.") - - project = subparsers.add_parser("project", help="Project registration and workspace utilities.") - project_sub = project.add_subparsers(dest="project_command", required=True) - project_register = project_sub.add_parser("register", help="Register a repo with obsmcp.") - project_register.add_argument("--repo", dest="repo_path", required=True) - project_register.add_argument("--name") - project_register.add_argument("--tags") - project_list = project_sub.add_parser("list", help="List registered projects.") - project_paths = project_sub.add_parser("paths", help="Show workspace paths for a project.") - project_paths.add_argument("--slug", dest="project_slug") - project_paths.add_argument("--repo", dest="project_repo") - project_migrate = project_sub.add_parser("migrate", help="Migrate legacy repo-local obsmcp notes/context into the centralized workspace.") - project_migrate.add_argument("--slug", dest="project_slug") - project_migrate.add_argument("--repo", dest="project_repo") - - session = subparsers.add_parser("session", help="Session operations.") - session_sub = session.add_subparsers(dest="session_command", required=True) - - session_open = session_sub.add_parser("open", help="Open a tracked AI work session.") - session_open.add_argument("--actor", required=True) - session_open.add_argument("--client", dest="client_name", default="") - session_open.add_argument("--model", dest="model_name", default="") - session_open.add_argument("--label", dest="session_label", default="") - session_open.add_argument("--workstream", dest="workstream_key", default="") - session_open.add_argument("--workstream-title", dest="workstream_title", default="") - session_open.add_argument("--project-path", dest="session_project_path", default=None) - session_open.add_argument("--initial-request", default="") - session_open.add_argument("--goal", dest="session_goal", default="") - session_open.add_argument("--task", dest="task_id") - session_open.add_argument("--resume-strategy", choices=["auto", "new", "resume"], default="auto") - session_open.add_argument("--resume-session-id", default=None) - session_open.add_argument("--heartbeat-seconds", dest="heartbeat_interval_seconds", type=int, default=900) - session_open.add_argument("--worklog-seconds", dest="work_log_interval_seconds", type=int, default=1800) - session_open.add_argument("--min-worklogs", dest="min_work_logs", type=int, default=1) - - session_heartbeat = session_sub.add_parser("heartbeat", help="Heartbeat an active session.") - session_heartbeat.add_argument("session_id") - session_heartbeat.add_argument("--actor", required=True) - session_heartbeat.add_argument("--note", dest="status_note", default="") - session_heartbeat.add_argument("--task", dest="task_id") - session_heartbeat.add_argument("--files") - session_heartbeat.add_argument("--create-work-log", action="store_true") - - session_close = session_sub.add_parser("close", help="Close a session and optionally create a handoff.") - session_close.add_argument("session_id") - session_close.add_argument("--actor", required=True) - session_close.add_argument("--summary", default="") - session_close.add_argument("--handoff-summary", default="") - session_close.add_argument("--handoff-next-steps", default="") - session_close.add_argument("--handoff-open-questions", default="") - session_close.add_argument("--handoff-note", default="") - session_close.add_argument("--handoff-to", dest="handoff_to_actor", default="next-agent") - session_close.add_argument("--skip-handoff", action="store_true") - - session_list = session_sub.add_parser("list", help="List active sessions.") - - atlas = subparsers.add_parser("atlas", help="Code Atlas — scan and document the entire codebase.") - atlas.add_argument("action", nargs="?", default="status", choices=["status", "refresh", "generate", "jobs", "job", "wait"], help="'status' = check atlas state. 'refresh' = regenerate if stale. 'generate' = force regenerate. 'jobs'/'job'/'wait' operate on background scan jobs.") - atlas.add_argument("job_id", nargs="?", default=None, help="Optional scan job ID for 'job' or 'wait'.") - atlas.add_argument("--force", action="store_true", help="Force full regeneration even if up to date.") - atlas.add_argument("--background", action="store_true", help="Queue the scan in the background and return a pollable job.") - atlas.add_argument("--wait", action="store_true", help="After queueing a background scan, wait for completion.") - atlas.add_argument("--wait-seconds", type=int, default=30, help="How long to wait when using --wait or atlas wait.") - atlas.add_argument("--requested-by", default="ctx", help="Actor label to store on a queued background scan job.") - atlas.add_argument("--status", dest="job_status", choices=["queued", "running", "completed", "failed", "interrupted"], help="Filter atlas jobs by status.") - - describe = subparsers.add_parser("describe", help="Semantic knowledge lookups.") - describe_sub = describe.add_subparsers(dest="describe_command", required=True) - describe_module = describe_sub.add_parser("module", help="Describe a module/file.") - describe_module.add_argument("module_path") - describe_symbol = describe_sub.add_parser("symbol", help="Describe a function or class.") - describe_symbol.add_argument("symbol_name") - describe_symbol.add_argument("--module") - describe_symbol.add_argument("--entity-key") - describe_symbol.add_argument("--type", dest="entity_type", choices=["function", "class"]) - describe_feature = describe_sub.add_parser("feature", help="Describe a feature tag.") - describe_feature.add_argument("feature_name") - - knowledge = subparsers.add_parser("knowledge", help="Semantic knowledge search and maintenance.") - knowledge_sub = knowledge.add_subparsers(dest="knowledge_command", required=True) - knowledge_search = knowledge_sub.add_parser("search", help="Search semantic knowledge.") - knowledge_search.add_argument("query") - knowledge_search.add_argument("--limit", type=int, default=10) - knowledge_candidates = knowledge_sub.add_parser("candidates", help="Get symbol candidates for a name.") - knowledge_candidates.add_argument("symbol_name") - knowledge_candidates.add_argument("--module") - knowledge_candidates.add_argument("--type", dest="entity_type", choices=["function", "class"]) - knowledge_candidates.add_argument("--limit", type=int, default=20) - knowledge_related = knowledge_sub.add_parser("related", help="Get related symbols for an entity.") - knowledge_related.add_argument("entity_key") - knowledge_related.add_argument("--limit", type=int, default=8) - knowledge_invalidate = knowledge_sub.add_parser("invalidate", help="Invalidate semantic cache by entity or file.") - knowledge_invalidate.add_argument("--entity-key") - knowledge_invalidate.add_argument("--files") - knowledge_refresh = knowledge_sub.add_parser("refresh", help="Force refresh a semantic description.") - knowledge_refresh.add_argument("--entity-key") - knowledge_refresh.add_argument("--module") - knowledge_refresh.add_argument("--symbol") - knowledge_refresh.add_argument("--feature") - knowledge_refresh.add_argument("--type", dest="entity_type", choices=["function", "class"]) - - # Phase 4: compact_context_v2 CLI - compact = subparsers.add_parser("compact", help="Generate compact context v2 with token budget.") - compact.add_argument("--task", dest="task_id") - compact.add_argument("--profile", choices=["fast", "balanced", "deep", "handoff", "recovery"], default="deep") - compact.add_argument("--max-tokens", dest="max_tokens", type=int, default=3000) - compact.add_argument("--no-decision-chain", dest="include_decision_chain", action="store_false", default=True) - compact.add_argument("--no-dependency-map", dest="include_dependency_map", action="store_false", default=True) - compact.add_argument("--no-session-info", dest="include_session_info", action="store_false", default=True) - compact.add_argument("--no-recent-work", dest="include_recent_work", action="store_false", default=True) - compact.add_argument("--daily-notes", action="store_true", default=False) - - delta = subparsers.add_parser("delta", help="Generate delta context since a handoff, session, or timestamp.") - delta.add_argument("--task", dest="task_id") - delta.add_argument("--handoff", dest="since_handoff_id", type=int) - delta.add_argument("--session", dest="since_session_id") - delta.add_argument("--since", dest="since_timestamp") - - audit = subparsers.add_parser("audit", help="Audit sessions for missing write-back and handoffs.") - audit.add_argument("--include-closed", action="store_true") - - fast = subparsers.add_parser("fast", help="Generate a lightweight L0-only fast context for startup/resume.") - fast.add_argument("--task", dest="task_id") - fast.add_argument("--tokens", action="store_true", help="Print token count after output.") - - resume = subparsers.add_parser("resume", help="Generate a resume packet for the active project/session.") - resume.add_argument("--session", dest="session_id") - resume.add_argument("--task", dest="task_id") - - recover = subparsers.add_parser("recover", help="Recover an interrupted session with an emergency handoff.") - recover.add_argument("--session", dest="session_id") - recover.add_argument("--actor", default="ctx-recovery") - - workspace = subparsers.add_parser("workspace", help="Workspace helper commands.") - workspace_sub = workspace.add_subparsers(dest="workspace_command", required=True) - workspace_paths = workspace_sub.add_parser("paths", help="Show the workspace paths for the active project.") - workspace_paths.add_argument("--slug", dest="project_slug") - workspace_paths.add_argument("--repo", dest="workspace_repo") - - hub = subparsers.add_parser("hub", help="Central hub utilities.") - hub_sub = hub.add_subparsers(dest="hub_command", required=True) - hub_sync = hub_sub.add_parser("sync", help="Refresh the obsmcp hub vault.") - - # Phase 1: Task Templates CLI - template = subparsers.add_parser("template", help="Task template operations.") - template_sub = template.add_subparsers(dest="template_command", required=True) - template_list = template_sub.add_parser("list", help="List all task templates.") - template_get = template_sub.add_parser("get", help="Get a specific template.") - template_get.add_argument("name") - template_create = template_sub.add_parser("create", help="Create a new task template.") - template_create.add_argument("name") - template_create.add_argument("--title", required=True, help="Title template with {placeholders}") - template_create.add_argument("--description", required=True, help="Description template with {placeholders}") - template_create.add_argument("--priority", default="medium") - template_create.add_argument("--tags") - template_delete = template_sub.add_parser("delete", help="Delete a task template.") - template_delete.add_argument("name") - - # Phase 1: Quick Log CLI - quick = subparsers.add_parser("quick", help="Quick work log — auto-tags current task.") - quick.add_argument("message") - quick.add_argument("--files") - quick.add_argument("--actor", default="ctx") - - # Phase 1: Audit Log CLI - audit_log = subparsers.add_parser("audit-log", help="Full activity timeline.") - audit_log.add_argument("--actor") - audit_log.add_argument("--task") - audit_log.add_argument("--type") - audit_log.add_argument("--from") - audit_log.add_argument("--to") - audit_log.add_argument("--limit", type=int, default=100) - audit_log.add_argument("--ai-only", action="store_true") - - # Phase 2: Reset Project CLI - reset = subparsers.add_parser("reset", help="Reset project data by scope. WARNING: permanently deletes data.") - reset.add_argument("--scope", required=True, choices=["tasks", "blockers", "sessions", "work_logs", "decisions", "handoffs", "full"], help="Scope to reset") - reset.add_argument("--actor", default="ctx") - - # Phase 2: Bulk Task Ops CLI - bulk = subparsers.add_parser("bulk", help="Bulk task operations (JSON array of operations).") - bulk.add_argument("operations", help='JSON array, e.g. \'[{"action":"create","title":"X","description":"Y"}]\'') - - # Phase 2: Project Export CLI - export = subparsers.add_parser("export", help="Export project state.") - export.add_argument("--format", default="both", choices=["json", "markdown", "both"]) - - # Phase 3: Work Log Expiry CLI - logs = subparsers.add_parser("logs", help="Work log operations.") - logs_sub = logs.add_subparsers(dest="logs_command", required=True) - logs_stats = logs_sub.add_parser("stats", help="Show log statistics by age.") - logs_expire = logs_sub.add_parser("expire", help="Purge old logs.") - logs_expire.add_argument("--days", type=int, help="Override retention days (default: use configured)") - logs_expire.add_argument("--actor", default="ctx") - logs_config = logs_sub.add_parser("config", help="Configure log expiry.") - logs_config.add_argument("days", type=int, help="Retention days (0=disable)") - - # Phase 3: Session Replay CLI - session_replay = subparsers.add_parser("replay", help="Replay a session timeline.") - session_replay.add_argument("session_id", nargs="?", help="Session ID (defaults to most recent)") - - # Phase 3: Task Dependency CLI - deps = subparsers.add_parser("deps", help="Task dependency operations.") - deps_sub = deps.add_subparsers(dest="deps_command", required=True) - deps_add = deps_sub.add_parser("add", help="Add dependency.") - deps_add.add_argument("task_id") - deps_add.add_argument("--blocked-by", help="Comma-separated task IDs this is blocked by") - deps_add.add_argument("--blocks", help="Comma-separated task IDs this blocks") - deps_remove = deps_sub.add_parser("remove", help="Remove dependency.") - deps_remove.add_argument("task_id") - deps_remove.add_argument("--blocked-by", help="Comma-separated task IDs to unlink") - deps_remove.add_argument("--blocks", help="Comma-separated task IDs to unlink") - deps_list = deps_sub.add_parser("list", help="List all dependencies.") - deps_blocked = deps_sub.add_parser("blocked", help="List blocked tasks.") - deps_validate = deps_sub.add_parser("validate", help="Validate all dependencies.") - - task = subparsers.add_parser("task", help="Task operations.") - task_sub = task.add_subparsers(dest="task_command", required=True) - - task_create = task_sub.add_parser("create", help="Create a new task.") - task_create.add_argument("title") - task_create.add_argument("--description", required=True) - task_create.add_argument("--priority", default="medium") - task_create.add_argument("--owner") - task_create.add_argument("--files") - task_create.add_argument("--tags") - task_create.add_argument("--actor", default="ctx") - - task_update = task_sub.add_parser("update", help="Update an existing task.") - task_update.add_argument("task_id") - task_update.add_argument("--title") - task_update.add_argument("--description") - task_update.add_argument("--status") - task_update.add_argument("--priority") - task_update.add_argument("--owner") - task_update.add_argument("--files") - task_update.add_argument("--tags") - task_update.add_argument("--actor", default="ctx") - - decision = subparsers.add_parser("decision", help="Decision operations.") - decision_sub = decision.add_subparsers(dest="decision_command", required=True) - - decision_log = decision_sub.add_parser("log", help="Record a decision.") - decision_log.add_argument("title") - decision_log.add_argument("--decision", required=True) - decision_log.add_argument("--rationale", default="") - decision_log.add_argument("--impact", default="") - decision_log.add_argument("--task", dest="task_id") - decision_log.add_argument("--actor", default="ctx") - - return parser - - -def main() -> None: - parser = build_parser() - args = parser.parse_args() - - config = load_config(args.config) - service = ObsmcpService(config) - project_path = args.project_path - - if args.command == "start": - _print(service.set_current_task(task_id=args.task_id, actor=args.actor, project_path=project_path)) - return - - if args.command == "log": - _print( - service.log_work( - message=args.message, - task_id=args.task_id, - summary=args.summary, - files=_csv(args.files), - actor=args.actor, - project_path=project_path, - ) - ) - return - - if args.command == "handoff": - task_id = args.task_id or (service.get_current_task(project_path=project_path) or {}).get("id") - _print( - service.create_handoff( - summary=args.summary, - next_steps=args.next_steps, - open_questions=args.open_questions, - note=args.note, - task_id=task_id, - from_actor=args.from_actor, - to_actor=args.to_actor, - project_path=project_path, - ) - ) - return - - if args.command == "sync": - _print(service.sync_context(project_path=project_path)) - return - - if args.command == "fast": - result = service.generate_fast_context(task_id=args.task_id, project_path=project_path) - if args.tokens: - print(result["markdown"], end="") - print(f"\n--- {result['used_tokens']} tokens ---") - else: - _print(result) - return - - if args.command == "status": - _print(service.get_project_status_snapshot(project_path=project_path)) - return - - if args.command == "preflight": - _print( - service.get_startup_preflight( - actor=args.actor, - task_id=args.task_id, - session_id=args.session_id, - initial_request=args.initial_request, - session_goal=args.session_goal, - session_label=args.session_label, - workstream_key=args.workstream_key, - client_name=args.client_name, - model_name=args.model_name, - project_path=project_path, - ) - ) - return - - if args.command == "resume-board": - _print(service.get_resume_board(project_path=project_path)) - return - - if args.command == "compat": - _print( - service.check_client_compatibility( - client_api_version=args.client_api_version, - client_tool_schema_version=args.client_tool_schema_version, - client_name=args.client_name, - model_name=args.model_name, - project_path=project_path, - ) - ) - return - - if args.command == "blockers": - _print(service.get_blockers(project_path=project_path)) - return - - if args.command == "note": - _print(service.create_daily_note_entry(entry=args.entry, actor=args.actor, note_date=args.note_date, project_path=project_path)) - return - - if args.command == "current": - _print(service.get_current_task(project_path=project_path) or {"message": "No current task set."}) - return - - if args.command == "project": - if args.project_command == "register": - _print(service.register_project(repo_path=args.repo_path, name=args.name, tags=_csv(args.tags))) - return - if args.project_command == "list": - _print(service.list_projects()) - return - if args.project_command == "paths": - _print(service.get_project_workspace_paths(project_slug=args.project_slug, project_path=args.project_repo or project_path)) - return - if args.project_command == "migrate": - _print(service.migrate_project_layout(project_slug=args.project_slug, project_path=args.project_repo or project_path)) - return - - if args.command == "session": - if args.session_command == "open": - effective_project_path = args.session_project_path or project_path - _print( - service.session_open( - actor=args.actor, - client_name=args.client_name, - model_name=args.model_name, - session_label=args.session_label, - workstream_key=args.workstream_key, - workstream_title=args.workstream_title, - project_path=effective_project_path, - initial_request=args.initial_request, - session_goal=args.session_goal, - task_id=args.task_id, - heartbeat_interval_seconds=args.heartbeat_interval_seconds, - work_log_interval_seconds=args.work_log_interval_seconds, - min_work_logs=args.min_work_logs, - resume_strategy=args.resume_strategy, - resume_session_id=args.resume_session_id, - ) - ) - return - if args.session_command == "heartbeat": - _print( - service.session_heartbeat( - session_id=args.session_id, - actor=args.actor, - status_note=args.status_note, - task_id=args.task_id, - files=_csv(args.files), - create_work_log=args.create_work_log, - project_path=project_path, - ) - ) - return - if args.session_command == "close": - _print( - service.session_close( - session_id=args.session_id, - actor=args.actor, - summary=args.summary, - create_handoff=not args.skip_handoff, - handoff_summary=args.handoff_summary, - handoff_next_steps=args.handoff_next_steps, - handoff_open_questions=args.handoff_open_questions, - handoff_note=args.handoff_note, - handoff_to_actor=args.handoff_to_actor, - project_path=project_path, - ) - ) - return - if args.session_command == "list": - _print(service.get_active_sessions(project_path=project_path)) - return - - if args.command == "atlas": - if args.action in {"status", None}: - _print(service.get_code_atlas_status(project_path=project_path)) - return - if args.action == "jobs": - _print(service.list_scan_jobs(project_path=project_path, status=args.job_status)) - return - if args.action == "job": - if not args.job_id: - raise SystemExit("atlas job requires JOB_ID") - _print(service.get_scan_job(args.job_id, project_path=project_path)) - return - if args.action == "wait": - if not args.job_id: - raise SystemExit("atlas wait requires JOB_ID") - _print(service.wait_for_scan_job(args.job_id, project_path=project_path, wait_seconds=args.wait_seconds)) - return - force = args.action == "generate" or args.force - if args.background: - job = service.start_scan_job(project_path=project_path, force_refresh=force, requested_by=args.requested_by) - if args.wait: - _print(service.wait_for_scan_job(job["id"], project_path=project_path, wait_seconds=args.wait_seconds)) - else: - _print(job) - return - _print(service.scan_codebase(force_refresh=force, project_path=project_path)) - return - - if args.command == "describe": - if args.describe_command == "module": - _print(service.describe_module(module_path=args.module_path, project_path=project_path)) - return - if args.describe_command == "symbol": - _print( - service.describe_symbol( - symbol_name=args.symbol_name, - module_path=args.module, - entity_key=args.entity_key, - entity_type=args.entity_type, - project_path=project_path, - ) - ) - return - if args.describe_command == "feature": - _print(service.describe_feature(feature_name=args.feature_name, project_path=project_path)) - return - - if args.command == "knowledge": - if args.knowledge_command == "search": - _print(service.search_code_knowledge(query=args.query, limit=args.limit, project_path=project_path)) - return - if args.knowledge_command == "candidates": - _print( - service.get_symbol_candidates( - symbol_name=args.symbol_name, - module_path=args.module, - entity_type=args.entity_type, - limit=args.limit, - project_path=project_path, - ) - ) - return - if args.knowledge_command == "related": - _print(service.get_related_symbols(entity_key=args.entity_key, limit=args.limit, project_path=project_path)) - return - if args.knowledge_command == "invalidate": - _print( - service.invalidate_semantic_cache( - entity_key=args.entity_key, - file_paths=_csv(args.files), - project_path=project_path, - ) - ) - return - if args.knowledge_command == "refresh": - _print( - service.refresh_semantic_description( - entity_key=args.entity_key, - module_path=args.module, - symbol_name=args.symbol, - feature_name=args.feature, - entity_type=args.entity_type, - project_path=project_path, - ) - ) - return - - if args.command == "compact": - if args.profile == "deep": - result = service.generate_compact_context_v2( - task_id=args.task_id, - max_tokens=args.max_tokens, - include_decision_chain=args.include_decision_chain, - include_dependency_map=args.include_dependency_map, - include_session_info=args.include_session_info, - include_recent_work=args.include_recent_work, - include_daily_notes=args.daily_notes, - project_path=project_path, - ) - else: - result = service.generate_context_profile( - profile=args.profile, - task_id=args.task_id, - max_tokens=args.max_tokens, - include_daily_notes=args.daily_notes, - project_path=project_path, - )["markdown"] - _print(result) - return - - if args.command == "delta": - _print( - service.generate_delta_context( - task_id=args.task_id, - since_handoff_id=args.since_handoff_id, - since_session_id=args.since_session_id, - since_timestamp=args.since_timestamp, - project_path=project_path, - ) - ) - return - - if args.command == "audit": - _print(service.detect_missing_writeback(include_closed=args.include_closed, project_path=project_path)) - return - - if args.command == "resume": - _print(service.generate_resume_packet(session_id=args.session_id, task_id=args.task_id, project_path=project_path)) - return - - if args.command == "recover": - _print(service.recover_session(session_id=args.session_id, actor=args.actor, project_path=project_path)) - return - - if args.command == "workspace": - if args.workspace_command == "paths": - _print(service.get_project_workspace_paths(project_slug=args.project_slug, project_path=args.workspace_repo or project_path)) - return - - if args.command == "hub": - if args.hub_command == "sync": - _print(service.sync_hub()) - return - - if args.command == "task": - if args.task_command == "create": - _print( - service.create_task( - title=args.title, - description=args.description, - priority=args.priority, - owner=args.owner, - relevant_files=_csv(args.files), - tags=_csv(args.tags), - actor=args.actor, - project_path=project_path, - ) - ) - return - if args.task_command == "update": - _print( - service.update_task( - task_id=args.task_id, - title=args.title, - description=args.description, - status=args.status, - priority=args.priority, - owner=args.owner, - relevant_files=_csv(args.files) if args.files is not None else None, - tags=_csv(args.tags) if args.tags is not None else None, - actor=args.actor, - project_path=project_path, - ) - ) - return - - if args.command == "decision": - if args.decision_command == "log": - _print( - service.log_decision( - title=args.title, - decision=args.decision, - rationale=args.rationale, - impact=args.impact, - task_id=args.task_id, - actor=args.actor, - project_path=project_path, - ) - ) - return - - # Phase 1: Template commands - if args.command == "template": - store = service._store(project_path) - if args.template_command == "list": - _print(store.get_task_templates()) - return - if args.template_command == "get": - result = store.get_task_template(args.name) - _print(result or {"error": f"Template '{args.name}' not found."}) - return - if args.template_command == "create": - _print( - store.create_task_template( - name=args.name, - title_template=args.title, - description_template=args.description, - priority=args.priority, - tags=_csv(args.tags), - ) - ) - return - if args.template_command == "delete": - deleted = store.delete_task_template(args.name) - _print({"deleted": deleted, "template": args.name}) - return - - # Phase 1: Quick log - if args.command == "quick": - _print(service.quick_log(message=args.message, files=_csv(args.files), actor=args.actor, project_path=project_path)) - return - - # Phase 1: Audit log - if args.command == "audit-log": - _print( - service.get_audit_log( - actor=args.actor, - task_id=args.task, - action_type=args.type, - from_date=getattr(args, "from"), - to_date=getattr(args, "to"), - limit=args.limit, - include_ai_only=args.ai_only, - project_path=project_path, - ) - ) - return - - # Phase 2: Reset project - if args.command == "reset": - _print(service.reset_project(scope=args.scope, actor=args.actor, project_path=project_path)) - return - - # Phase 2: Bulk task operations - if args.command == "bulk": - import json as _json - - ops = _json.loads(args.operations) - _print(service.bulk_task_ops(operations=ops, project_path=project_path)) - return - - # Phase 2: Project export - if args.command == "export": - _print(service.export_project(format=args.format, project_path=project_path)) - return - - # Phase 3: Work log expiry - if args.command == "logs": - if args.logs_command == "stats": - _print(service.get_log_stats(project_path=project_path)) - return - if args.logs_command == "expire": - if args.days is not None: - service.configure_log_expiry(days=args.days, actor=args.actor, project_path=project_path) - _print(service.expire_old_logs(actor=args.actor, project_path=project_path)) - return - if args.logs_command == "config": - _print(service.configure_log_expiry(days=args.days, actor="ctx", project_path=project_path)) - return - - # Phase 3: Session replay - if args.command == "replay": - _print(service.session_replay(session_id=args.session_id, project_path=project_path)) - return - - # Phase 3: Task dependencies - if args.command == "deps": - if args.deps_command == "add": - blocked = [x.strip() for x in (args.blocked_by or "").split(",") if x.strip()] - blocks = [x.strip() for x in (args.blocks or "").split(",") if x.strip()] - _print(service.add_task_dependency(task_id=args.task_id, blocked_by=blocked, blocks=blocks, project_path=project_path)) - return - if args.deps_command == "remove": - blocked = [x.strip() for x in (args.blocked_by or "").split(",") if x.strip()] - blocks = [x.strip() for x in (args.blocks or "").split(",") if x.strip()] - _print(service.remove_task_dependency(task_id=args.task_id, blocked_by=blocked if blocked else None, blocks=blocks if blocks else None, project_path=project_path)) - return - if args.deps_command == "list": - _print(service.get_all_dependencies(project_path=project_path)) - return - if args.deps_command == "blocked": - _print(service.get_blocked_tasks(project_path=project_path)) - return - if args.deps_command == "validate": - _print(service.validate_dependencies(project_path=project_path)) - return - - parser.error("Unknown command") - - -if __name__ == "__main__": - main() diff --git a/config/mcp-client-example.json b/config/mcp-client-example.json deleted file mode 100644 index 72a099c..0000000 --- a/config/mcp-client-example.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "mcpServers": { - "obsmcp": { - "transport": "http", - "url": "http://127.0.0.1:9300/mcp" - } - } -} diff --git a/config/obsmcp.json b/config/obsmcp.json deleted file mode 100644 index 8ce62c2..0000000 --- a/config/obsmcp.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "app_name": "obsmcp", - "description": "Obsidian MCP continuity server", - "host": "127.0.0.1", - "port": 9300, - "bind_local_only": true, - "database_path": "./data/db/obsmcp.sqlite3", - "json_export_dir": "./data/json", - "backup_dir": "./data/backups", - "log_dir": "./logs", - "context_dir": "./.context", - "obsidian_vault_dir": "./obsidian/vault", - "pid_file": "./data/obsmcp.pid", - "workspace_root_dir": "./workspace", - "projects_root_dir": "./projects", - "hub_vault_dir": "./hub/vault", - "registry_path": "./registry/projects.json", - "repo_bridge_filename": ".obsmcp-link.json", - "max_recent_work_items": 12, - "max_decisions": 20, - "max_blockers": 20, - "strict_project_routing": true, - "bootstrap_default_project_on_startup": false, - "obsidian": { - "project_brief_note": "Projects/Project Brief.md", - "current_task_note": "Projects/Current Task.md", - "status_snapshot_note": "Projects/Status Snapshot.md", - "latest_handoff_note": "Handoffs/Latest Handoff.md", - "decision_index_note": "Decisions/Decision Log.md", - "daily_notes_dir": "Daily", - "session_note": "Sessions/Latest Session Summary.md", - "code_atlas_note": "Research/Code Atlas.md" - }, - "default_project_path": "D:\\Projects\\obsmcp", - "logging": { - "level": "INFO", - "json_output": true, - "json_output_path": "obsmcp-structured.json", - "include_traceback": false, - "console_output": true - }, - "checkpoints": { - "enabled": true, - "render_limit": 12, - "auto_rollup": true, - "auto_close_task": false - }, - "output_compression": { - "enabled": false, - "mode": "off", - "level": "full", - "style": "concise_professional", - "respect_user_detail_requests": true, - "expand_on_request": true, - "task_overrides": { - "review": { - "style": "terse_technical", - "level": "full" - }, - "debugging": { - "style": "concise_professional", - "level": "full" - }, - "architecture": { - "style": "concise_professional", - "level": "lite" - }, - "dangerous_actions": { - "mode": "off" - } - }, - "prompt_only": { - "enabled": true, - "direct_answer_first": true, - "no_greetings": true, - "no_recap": true, - "short_paragraphs": true, - "prefer_bullets_for_lists": true, - "max_paragraph_sentences": 3, - "findings_first_for_reviews": true - }, - "gateway_enforced": { - "enabled": false, - "inject_contract": true, - "enforce_direct_answer_first": true, - "enforce_findings_first_for_reviews": true, - "max_output_tokens_soft": 900, - "max_output_sections": 6, - "max_paragraph_lines": 4 - }, - "safety_bypass": { - "enabled": true, - "destructive_actions": true, - "security_sensitive": true, - "legal_medical_financial": true, - "ambiguity_clarification": true, - "step_by_step_sensitive": true - }, - "observability": { - "log_metrics": true, - "record_mode": true, - "record_style": true, - "record_task_type": true, - "sample_rate": 1.0 - }, - "preserve_patterns": { - "code_blocks": true, - "urls": true, - "filepaths": true, - "error_messages": true, - "json_output": true, - "commands": true, - "stack_traces": true - } - }, - "semantic": { - "auto_generate": { - "enabled": true, - "allow_llm": true, - "max_modules_per_scan": 5, - "max_modules_per_write": 3, - "on_log_work": true, - "on_update_task": true, - "on_create_task": true, - "on_set_current_task": true, - "on_handoff": true, - "on_startup": true, - "max_queue_size": 8, - "max_concurrent_jobs": 1, - "wait_ms_on_handoff": 250, - "wait_ms_on_startup": 150, - "skip_path_fragments": [ - "/.git/", - "/.hg/", - "/.svn/", - "/.venv/", - "/venv/", - "/node_modules/", - "/dist/", - "/build/", - "/target/", - "/vendor/", - "/__pycache__/", - "/coverage/", - "/.next/" - ], - "skip_generated_suffixes": [ - ".min.js", - ".bundle.js", - ".generated.ts", - ".generated.js", - ".generated.py", - ".pb.go", - ".designer.cs" - ] - } - } -} diff --git a/ctx.bat b/ctx.bat deleted file mode 100644 index 6922aa7..0000000 --- a/ctx.bat +++ /dev/null @@ -1,11 +0,0 @@ -@echo off -setlocal -set "OBSMCP_CALLER_CWD=%CD%" -if not defined OBSMCP_PROJECT set "OBSMCP_PROJECT=%OBSMCP_CALLER_CWD%" -cd /d "%~dp0" - -if exist ".venv\Scripts\python.exe" ( - ".venv\Scripts\python.exe" -m cli.main %* -) else ( - py -3 -m cli.main %* -) diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..373a121 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,22 @@ +services: + obsmcp: + build: . + image: obsmcp:latest + ports: + - "8000:8000" + environment: + - OBSMCP_API_TOKEN=${OBSMCP_API_TOKEN:-} + - OBSMCP_DB_PATH=/data/obsmcp.db + - OBSMCP_HOST=0.0.0.0 + - OBSMCP_PORT=8000 + volumes: + - obsmcp-data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "python", "-c", "import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://localhost:8000/healthz').status==200 else 1)"] + interval: 30s + timeout: 5s + retries: 3 + +volumes: + obsmcp-data: diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md deleted file mode 100644 index a3e050a..0000000 --- a/docs/ARCHITECTURE.md +++ /dev/null @@ -1,201 +0,0 @@ -# obsmcp Architecture - -## Why this architecture - -`obsmcp` now uses a centralized project-workspace architecture: - -- one global control plane rooted at `C:\obsmcp` when deployed -- one isolated workspace per registered project under `projects//` -- SQLite as the per-project system of record -- generated `.context` files as the universal low-friction handoff surface -- per-project Obsidian vaults as the human-readable knowledge layer -- a central hub vault for cross-project visibility -- a local MCP-compatible HTTP server as the structured integration point -- the `ctx` CLI as the fallback bridge for tools that do not speak MCP - -This layout is resilient because each layer has one job and can be repaired independently. - -## System layers - -### 1. Project workspace model - -Each project resolves to a centralized workspace: - -- `projects//data/db/obsmcp.sqlite3` -- `projects//.context/` -- `projects//vault/` -- `projects//sessions/` -- `projects//logs/` -- `projects//project.json` - -The repo stays the code source; the workspace becomes the continuity source. - -### 2. System of record - -SQLite stores: - -- project brief sections -- current task pointer -- tasks -- work logs -- decisions -- blockers -- handoffs -- session summaries -- daily entries -- agent activity -- session labels and workstream metadata for human-readable project management - -Why SQLite: - -- zero external service dependency -- stable on Windows -- easy backup and inspection -- easy repair with a single file copy strategy -- enough concurrency for local multi-tool workflows - -### 3. Universal continuity files - -Each project workspace has its own `.context` directory, regenerated from SQLite after every write. It exists specifically for tools that: - -- cannot connect to MCP -- can only read files -- can only accept pasted prompt context - -This is the lowest common denominator continuity layer. - -Milestone B extends this layer with cached hot-path artifacts: - -- `HOT_CONTEXT.md` for fast startup -- `BALANCED_CONTEXT.md` for normal coding continuity -- `DEEP_CONTEXT.md` for architecture/debugging sessions -- `DELTA_CONTEXT.md` for "what changed since the last handoff/session" - -These are regenerated during sync so MCP reads and file-based fallbacks can both stay fast. - -### 4. Obsidian knowledge layer - -Obsidian receives generated project notes plus daily note entries and ADR-style decision notes. Each project gets its own vault, and the hub vault summarizes all registered projects. The project vault is for: - -- human review -- research capture -- debugging notes -- handoff reading -- operational memory outside the token window of any single model - -### 5. MCP server - -The MCP server binds locally on `127.0.0.1:9300` and routes requests into project workspaces. It exposes: - -- read tools for brief, task, blockers, decisions, handoffs, notes, and status -- write tools for work logs, tasks, decisions, blockers, handoffs, daily entries, and project brief sections -- meta tools for health, listing, compact context generation, task snapshots, resume packets, recovery, project registration, and hub sync -- resource endpoints for brief, current task, handoff, status, compact context, resume packets, and project listing - -Routing is continuity-aware, not just path-aware: - -- explicit `project_path` and `project_slug` always win -- `session_id` and `task_id` can route later writes back into the correct project automatically -- repo bridge files and absolute file paths can be used to infer the correct project workspace when a plugin does not pass `project_path` -- `cwd` and the nearest repo root can be used as first-call routing hints when a client starts inside the project -- IDE clients can call `resolve_active_project` up front with metadata such as `cwd`, `active_file`, `workspace_folders`, `open_files`, and environment hints to get a stable project scope before the first write -- recent matching sessions can be resumed automatically on `session_open` -- `session_open` can attach a readable `session_label` and stable `workstream_key` so logs and dashboards stay understandable to humans -- `session_open` now normalizes client/model identity strings and applies a mismatch guard before auto-resuming a candidate session -- if no reliable project hint exists, continuity-sensitive MCP calls fail fast instead of silently falling back to the default project - -### 6. CLI bridge - -`ctx.bat` calls the same service layer as the server, so the CLI still works if the server is down. This is intentional. The continuity system should not collapse just because the MCP listener is unavailable. - -It now also handles: - -- project registration -- workspace path inspection -- resume packet generation -- interrupted-session recovery -- hub refresh -- startup preflight and resume-board views -- compatibility checks between client expectations and server tool schema - -Server startup is intentionally lazy now: - -- booting `obsmcp` does not create a default project workspace unless `bootstrap_default_project_on_startup=true` -- global `health_check` can report server readiness without creating per-project state -- project workspaces are created only when a project is explicitly registered, resolved, or otherwise used with a real project scope - -## Why this is bulletproof - -- Local-only by default: binds to `127.0.0.1` -- One source of truth: all writes land in SQLite first -- Atomic file writes: generated files are rewritten atomically -- Small dependency set: only `fastapi` and `uvicorn` beyond the standard library -- Restart-safe: startup and stop scripts use PID tracking and Task Scheduler integration -- Per-project durability: sessions also write metadata, heartbeat, worklog, and handoff files into `sessions//` -- Session reuse: reopened tools can resume recent matching sessions instead of spawning unnecessary parallel sessions -- Human-readable session management: labels and workstreams make open/closed session history legible without decoding session ids -- Safer startup: preflight warnings and resume boards expose stale sessions, done current tasks, handoff mismatches, and taskless substantive work -- Recovery-aware audits: stale and abandoned sessions are flagged so another model can recover them cleanly -- Easy inspection: every important artifact is just SQLite, JSON, Markdown, batch, or Python -- Vendor-neutral: MCP, file reads, CLI, and prompt injection all work from the same state - -## Why this is token-efficient - -- project workspaces keep compact current-state files instead of full note dumps -- `generate_compact_context` creates a short prompt-ready summary -- `generate_context_profile` assembles tiered `fast`, `balanced`, `deep`, `handoff`, and `recovery` context variants from the same state -- `generate_delta_context` lets the next model read only what changed since the previous reference point -- `generate_resume_packet` creates a first-read handoff packet for the next model -- relevant files are tracked explicitly -- recent work is bounded -- decisions and blockers are summarized rather than replaying full history -- handoffs are auto-enriched with task state, relevant files, and semantic suggestions instead of relying only on long freeform prose - -## Output-token policy layer - -Output-token reduction is implemented as a separate response-policy layer and not as part of the continuity stack. - -This separation is deliberate: - -- continuity, handoffs, logs, delta context, retrieval context, prompt segments, and semantic caches keep their original fidelity -- output-token savings target only model-generated prose -- post-generation compaction remains auxiliary because it does not save model output tokens that were already generated - -The control plane lives in `config/obsmcp.json` under `output_compression` and supports: - -- `off` -- `prompt_only` -- `gateway_enforced` - -Current enforcement scope is intentionally narrow and honest: - -- `prompt_only` and `gateway_enforced` affect `generate_startup_prompt_template` -- `gateway_enforced` also affects LLM-backed semantic descriptions by appending the enforced response contract to the OpusMax text-provider system prompt -- task overrides and safety bypass rules are resolved before contract generation so review/debugging/architecture tasks can use different brevity styles without touching context assembly - -This means `obsmcp` saves output tokens only where it actually owns the generation boundary today, while preserving the existing input-token-saving architecture intact. - -## Milestone B performance hardening - -Milestone B adds a dedicated context artifact cache in SQLite plus generated workspace files. The service now: - -- computes a project-local state version from task/work/blocker/handoff/session changes -- caches tiered context artifacts keyed by profile, task scope, and token budget -- reuses cached context when the project state has not changed -- writes fresh tiered context files into `.context` and `data/json` during sync -- exposes a delta view so resumed agents do not need to replay unchanged project history - -This improves latency without sacrificing continuity because the underlying source of truth remains SQLite. - -## Why this is especially strong for cross-model continuity - -When one model stops halfway, the next model can recover from any of these layers: - -- MCP tools: structured read of task, blockers, handoff, decisions, files -- per-project `.context`: immediate file-based continuity with no protocol support required -- per-project session folders: metadata, heartbeat timeline, worklog, resume packet, and emergency handoff files -- Obsidian project vault: human-readable history and architecture notes -- hub vault: cross-project visibility and quick switching -- CLI: quick manual updates and state sync from any shell-capable tool - -The key design choice is that continuity is not attached to one client. It is attached to `obsmcp`. diff --git a/docs/FOLDER_STRUCTURE.md b/docs/FOLDER_STRUCTURE.md deleted file mode 100644 index d5cf741..0000000 --- a/docs/FOLDER_STRUCTURE.md +++ /dev/null @@ -1,60 +0,0 @@ -# Folder Structure - -Recommended production install path: `C:\obsmcp` - -This repository is portable, but `C:\obsmcp` is the cleanest Windows deployment path because the batch scripts, scheduled task, and mental model stay simple. - -## Layout - -```text -C:\obsmcp\ - server\ Python MCP server, state layer, sync engine - cli\ ctx CLI implementation - scripts\ operational helpers for launch, stop, backup - config\ JSON config and example integration snippets - logs\ rotating global server logs and startup logs - docs\ install, usage, architecture, testing, troubleshooting docs - templates\ - obsidian\ note templates and examples - context\ context and prompt templates - registry\ - projects.json global registry of known projects - hub\ - vault\ central Obsidian hub vault for all projects - projects\ - \ - project.json per-project manifest - data\ - db\ per-project SQLite database - json\ per-project exported snapshots - backups\ per-project backups - exports\ per-project export bundles - .context\ per-project continuity files - vault\ per-project Obsidian vault - sessions\ per-session folders with metadata, worklog, and handoff files - logs\ per-project logs - tests\ local automated verification - tools\ universal instructions and helper assets -``` - -## Folder purpose - -- `server`: the production code that owns state, sync, routing, recovery, and MCP handling -- `cli`: the `ctx` command surface for shells and non-MCP tools -- `scripts`: Windows operational actions such as detached launch and backup -- `config`: editable settings without touching code -- `logs`: global server, error, and startup logs -- `docs`: the operator handbook -- `templates`: reusable note, prompt, and continuity templates -- `registry/projects.json`: global list of registered repos and their centralized workspaces -- `hub/vault`: top-level dashboard vault for all projects -- `projects//data/db`: source-of-truth SQLite file for that project -- `projects//data/json`: per-project snapshots for debugging or external integrations -- `projects//data/backups`: copy-based backup targets for that project -- `projects//data/exports`: markdown/json export bundles -- `projects//.context`: the minimum viable continuity package every tool should read first -- `projects//vault`: the human-facing project brain in Markdown -- `projects//sessions`: durable session folders for recovery and handoff -- `projects//logs`: project-local logs -- `tests`: lightweight regression coverage for state, sync, registry, and recovery behavior -- `tools`: copy-paste onboarding instructions for AI assistants diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md deleted file mode 100644 index 2090152..0000000 --- a/docs/INSTALLATION.md +++ /dev/null @@ -1,91 +0,0 @@ -# Installation Guide - -## Recommended install location - -Place the repository at: - -```text -C:\obsmcp -``` - -This keeps batch scripts, Task Scheduler paths, and human troubleshooting straightforward. - -## Prerequisites - -- Windows -- Python `3.11+` -- PowerShell or Command Prompt -- Obsidian installed locally if you want the live vault workflow - -## Install steps - -From the project root: - -```bat -cd /d C:\obsmcp -bootstrap_obsmcp.bat -``` - -This will: - -- create `.venv` -- upgrade `pip` -- install `fastapi` and `uvicorn` - -## Start locally - -```bat -start_obsmcp.bat -``` - -This launches `obsmcp` in the background on: - -```text -http://127.0.0.1:9300 -``` - -## Stop locally - -```bat -stop_obsmcp.bat -``` - -## Verify local health - -```bat -curl http://127.0.0.1:9300/healthz -netstat -ano | findstr :9300 -ctx.bat project list -``` - -## Register a project - -The server is installed once, but each repo gets its own centralized workspace under `projects//`. - -Register a repo: - -```bat -ctx.bat project register --repo D:\Work\MyApp --name "My App" -``` - -Inspect the workspace: - -```bat -ctx.bat project paths --repo D:\Work\MyApp -``` - -If the repo already has older repo-local `.context` or `obsidian\vault` content, migrate it: - -```bat -ctx.bat project migrate --repo D:\Work\MyApp -``` - -## Optional security hardening - -By default, `obsmcp` binds to `127.0.0.1`, which is the safest local-first default. If you want an extra local token requirement for HTTP requests, set: - -```bat -set OBSMCP_API_TOKEN=your-local-token -``` - -Then pass `Authorization: Bearer your-local-token` to non-health HTTP requests. diff --git a/docs/NEXT_COMMIT_PLAN.md b/docs/NEXT_COMMIT_PLAN.md deleted file mode 100644 index 62c813b..0000000 --- a/docs/NEXT_COMMIT_PLAN.md +++ /dev/null @@ -1,79 +0,0 @@ -# Next Commit Plan - -This document captures the most useful next commit after the public repo polish pass. - -## Recommended next commit - -Recommended primary follow-up: - -`VS Code startup integration + output-token strategy surfacing` - -Why this is the best next move: - -- the new workflow-safety features are already implemented server-side -- the highest leverage now is getting clients to actually use them by default -- this improves the real day-one user experience more than adding another backend feature - -## Track A: VS Code integration improvements - -Goal: - -- make the VS Code / Claude Code / Codex startup flow safer by default - -Suggested work: - -1. call `resolve_active_project` before continuity-sensitive reads -2. call `get_startup_preflight` on session startup -3. call `get_resume_board` when resuming or reopening a project -4. infer and pass: - - `session_label` - - `workstream_key` - - `client_name` - - `model_name` -5. default to `resume_strategy=new` when the new prompt is clearly unrelated to the prior workstream - -Expected result: - -- fewer accidental resumes -- clearer session naming -- better first-run trust in `obsmcp` - -## Track B: Output-token strategy improvements - -Goal: - -- make output-token optimization easier to understand and adopt - -Suggested work: - -1. expose a compact "recommended output mode" helper for clients -2. add more docs/examples for: - - `off` - - `prompt_only` - - `gateway_enforced` -3. surface token savings in a more visible project dashboard or fast-path response -4. add task-type presets for: - - review - - docs - - debugging - - architecture - -Expected result: - -- easier rollout of output-token reduction -- clearer understanding of where token savings really happen - -## Recommended commit message - -```text -Improve VS Code startup flow and expose output-token strategy defaults -``` - -## Files likely involved - -- `server/service.py` -- `cli/main.py` -- `docs/USAGE.md` -- `docs/ARCHITECTURE.md` -- `config/obsmcp.json` -- IDE/client integration config files diff --git a/docs/OBSIDIAN.md b/docs/OBSIDIAN.md deleted file mode 100644 index 9bc4495..0000000 --- a/docs/OBSIDIAN.md +++ /dev/null @@ -1,99 +0,0 @@ -# Obsidian Integration - -## Integration mode - -The safe default is filesystem-based vault integration. `obsmcp` does not require the Obsidian Local REST API. - -Why this is the default: - -- fewer moving parts -- no plugin dependency for the core path -- safe local writes using generated machine-owned files -- easier recovery and debugging - -## Centralized vault model - -`obsmcp` uses two vault layers: - -- project vaults under `projects//vault` -- a central hub vault under `hub/vault` - -Project vaults are the canonical note home for each repo. The hub vault is a dashboard used to monitor all registered projects without mixing their detailed notes together. - -## What lives in structured state vs Obsidian - -### Structured state in SQLite - -- current task pointer -- tasks and task metadata -- blockers -- decisions -- work logs -- handoffs -- session summaries -- daily entries -- agent activity - -### Obsidian vault - -- generated project brief -- generated current task note -- generated status snapshot -- generated latest handoff note -- generated decision index -- generated ADR notes -- generated latest session summary -- appended daily note entries -- generated architecture map -- generated module summaries -- generated feature map -- generated symbol knowledge notes for cached semantic descriptions -- human-created research, debug, SOP, and architecture notes - -## Safe write pattern - -`obsmcp` only fully rewrites machine-owned generated notes: - -- `Projects/Project Brief.md` -- `Projects/Current Task.md` -- `Projects/Status Snapshot.md` -- `Handoffs/Latest Handoff.md` -- `Decisions/Decision Log.md` -- generated `Decisions/ADR-xxxx.md` -- `Sessions/Latest Session Summary.md` -- `Research/Architecture Map.md` -- `Research/Module Summaries.md` -- `Research/Feature Map.md` -- generated `Research/Symbol Knowledge/*.md` - -It does not need to overwrite user-authored research or debug notes. - -## Project vault structure - -```text -projects//vault/ - Projects/ - Handoffs/ - Decisions/ - Daily/ - Research/ - Symbol Knowledge/ - Debug/ - Sessions/ -``` - -## Hub vault structure - -```text -hub/vault/ - Projects Overview.md - Active Projects.md -``` - -## Daily note strategy - -`ctx.bat --project D:\Work\MyApp note "message"` writes an entry to structured state and syncs it into `projects//vault/Daily/YYYY-MM-DD.md`. - -## Optional future extension - -If you later want deeper Obsidian automation, you can add the Local REST API as an optional integration layer, but it should stay optional. The filesystem path is the safer baseline. diff --git a/docs/OPUSMAX_TOOL_AUDIT.md b/docs/OPUSMAX_TOOL_AUDIT.md deleted file mode 100644 index 225d85c..0000000 --- a/docs/OPUSMAX_TOOL_AUDIT.md +++ /dev/null @@ -1,63 +0,0 @@ -# OpusMax Tool Audit - -This note captures the verified dependency points before the `OpusMax` MCP server is removed from Claude and replaced with `obsmcp`-backed tool calls. - -## Verified current state - -1. Claude currently has two MCP servers at the user level: - - `OpusMax` - - `obsmcp` - -2. Claude still routes its core model API traffic through OpusMax from the user-level Claude settings file: - - `~/.claude/settings.json` - - `ANTHROPIC_BASE_URL=https://api.opusmax.pro` - -3. `obsmcp` already depends on OpusMax for semantic description generation: - - [server/llm_client.py](../server/llm_client.py) - -4. Before this implementation pass, `obsmcp` did not expose first-class MCP tools for: - - web search - - image understanding - -## Implemented in phases 2-5 - -1. Added an internal OpusMax provider abstraction: - - [server/opusmax_provider.py](../server/opusmax_provider.py) - -2. Kept semantic descriptions working through the provider abstraction: - - [server/llm_client.py](../server/llm_client.py) - -3. Added `obsmcp` service methods and MCP tool exposure for: - - `web_search` - - `understand_image` - - [server/service.py](../server/service.py) - -4. Added tests for: - - provider adapters - - service-level tool exposure and provider-usage logging - - [tests/test_opusmax_provider.py](../tests/test_opusmax_provider.py) - - [tests/test_service.py](../tests/test_service.py) - -## Cutover Status: COMPLETE (2026-04-13) - -1. ✅ Direct `OpusMax` MCP server entry was already absent from `~/.claude.json` -2. ✅ obsmcp is the sole MCP server at `http://127.0.0.1:9300/mcp` -3. ✅ web_search and understand_image tools work through obsmcp -4. ✅ Token usage tracking recorded for both operations - -## Remaining Direct Dependencies - -1. Claude's core API routing in `~/.claude/settings.json` still uses: - - `ANTHROPIC_BASE_URL=https://api.opusmax.pro` - - `ANTHROPIC_AUTH_TOKEN=sk-ant-opm-...` (OpusMax API key) - - This is **intentional** - keeps OpusMax as the API gateway for all Claude API calls. - -2. obsmcp's semantic descriptions in `server/llm_client.py` still call OpusMax API directly via `OpusMaxTextProvider` for LLM-powered code descriptions. - -## Verified Working (2026-04-13) - -- web_search: 4 recorded events, 10 results per query, ~2.5s latency -- understand_image: 1 recorded event, accurate image analysis, ~9s latency -- Both tools track provider usage metrics via get_token_usage_stats -- HTTP(S) URL images may return error 2013 - use base64 or file paths instead \ No newline at end of file diff --git a/docs/STARTUP.md b/docs/STARTUP.md deleted file mode 100644 index 4d86551..0000000 --- a/docs/STARTUP.md +++ /dev/null @@ -1,70 +0,0 @@ -# Startup and Reboot Recovery - -## Manual start - -```bat -start_obsmcp.bat -``` - -If the detached launcher is blocked by local Windows policy or shell behavior, use this fallback to keep `obsmcp` alive in its own console window: - -```powershell -Start-Process -FilePath cmd.exe -ArgumentList '/k','cd /d D:\Projects\obsmcp && .venv\Scripts\python.exe -m server.main' -``` - -## Manual stop - -```bat -stop_obsmcp.bat -``` - -## Automatic startup on login - -Default recommended method: - -```bat -install_task_scheduler.bat -``` - -This creates a Windows Task Scheduler task named `obsmcp` that runs on logon with a short delay. - -## Remove automatic startup - -```bat -uninstall_task_scheduler.bat -``` - -## Why Task Scheduler is the default - -- more reliable than the Startup folder for background processes -- easier to inspect and repair -- easy to disable without editing files -- works cleanly with a batch launcher - -## Reboot recovery flow - -After reboot: - -1. Windows logon triggers the `obsmcp` scheduled task -2. `start_obsmcp.bat` launches the detached Python server -3. per-project workspaces under `projects//` remain intact -4. project `.context`, session folders, and vault files are still available -5. any new tool can resume from the project workspace or the hub vault - -## Manual recovery if startup is not enabled - -```bat -start_obsmcp.bat -ctx.bat project list -``` - -If `start_obsmcp.bat` exits but port `9300` does not stay open, use the dedicated console-window fallback above and then verify with `curl http://127.0.0.1:9300/healthz`. - -## Logs - -Check: - -- `logs/startup.log` -- `logs/obsmcp.log` -- `logs/obsmcp-error.log` -- `projects//logs` diff --git a/docs/TESTING.md b/docs/TESTING.md deleted file mode 100644 index 4572c06..0000000 --- a/docs/TESTING.md +++ /dev/null @@ -1,136 +0,0 @@ -# Testing Guide - -## Automated tests - -Run: - -```bat -.venv\Scripts\python.exe -m unittest discover -s tests -v -``` - -## Manual health test - -```bat -curl http://127.0.0.1:9300/healthz -``` - -Expected: - -- JSON response -- `port` is `9300` -- `db_exists` is `true` - -## Project registration test - -```bat -ctx.bat project register --repo D:\Work\MyApp --name "My App" -ctx.bat project paths --repo D:\Work\MyApp -ctx.bat hub sync -``` - -Expected: - -- a workspace appears under `projects//` -- a repo bridge file appears at `D:\Work\MyApp\.obsmcp-link.json` -- the hub vault is refreshed - -## CLI test - -```bat -ctx.bat --project D:\Work\MyApp task create "Test continuity" --description "Verify ctx write path" -ctx.bat --project D:\Work\MyApp session open --actor tester --client cli --model local --project-path D:\Work\MyApp --initial-request "Verify continuity" --goal "Exercise session tracking" -ctx.bat --project D:\Work\MyApp status -ctx.bat --project D:\Work\MyApp current -ctx.bat --project D:\Work\MyApp audit -ctx.bat --project D:\Work\MyApp resume -``` - -## Obsidian sync test - -1. Run `ctx.bat --project D:\Work\MyApp note "Testing daily note sync"` -2. Open `projects//vault/Daily/.md` -3. Confirm the entry appears - -## Semantic knowledge test - -```bat -ctx.bat --project D:\Work\MyApp atlas generate -ctx.bat --project D:\Work\MyApp describe module server\service.py -ctx.bat --project D:\Work\MyApp describe symbol generate_resume_packet --module server\service.py --type function -ctx.bat --project D:\Work\MyApp knowledge search "resume packet" -``` - -Expected: - -- semantic descriptions are returned -- `projects//vault/Research/Architecture Map.md` exists -- `projects//vault/Research/Module Summaries.md` exists -- `projects//vault/Research/Feature Map.md` exists -- `projects//vault/Research/Symbol Knowledge/` contains generated notes - -## Tiered context and delta test - -```bat -ctx.bat --project D:\Work\MyApp compact --profile fast --max-tokens 1200 -ctx.bat --project D:\Work\MyApp compact --profile balanced --max-tokens 2500 -ctx.bat --project D:\Work\MyApp compact --profile deep --max-tokens 4500 -ctx.bat --project D:\Work\MyApp delta -``` - -Expected: - -- the compact commands return progressively richer context variants -- repeated calls on unchanged state should return quickly from cache -- `delta` shows only changes since the latest handoff/session reference -- `projects//.context/HOT_CONTEXT.md` exists -- `projects//.context/BALANCED_CONTEXT.md` exists -- `projects//.context/DEEP_CONTEXT.md` exists -- `projects//.context/DELTA_CONTEXT.md` exists - -## Background scan job test - -```bat -ctx.bat --project D:\Work\MyApp atlas generate --background -ctx.bat --project D:\Work\MyApp atlas jobs -ctx.bat --project D:\Work\MyApp atlas wait SCAN-REPLACE-ME --wait-seconds 60 -``` - -Expected: - -- the first command returns a `SCAN-...` job ID quickly -- `atlas jobs` shows the job as `queued`, `running`, or `completed` -- `atlas wait` eventually returns `completed` -- after completion, `ctx.bat --project D:\Work\MyApp atlas status` shows the updated atlas metadata - -## Reboot persistence test - -1. Run `install_task_scheduler.bat` -2. Reboot or sign out and back in -3. Run `curl http://127.0.0.1:9300/healthz` -4. Confirm the server is up without manual start - -## Multi-tool continuity test - -1. Register the repo and inspect the workspace paths -2. Create a task and set it current -3. Open a session -4. Log work and create a handoff -5. Close the session -6. Open another tool that can read files -7. Confirm it can continue from `projects//.context` - -## Cross-model handoff test - -1. In tool A, open a session and create a task -2. In tool A, log work and run `ctx.bat --project D:\Work\MyApp handoff ...` -3. In tool A, close the session -4. In tool B, read `projects//.context/HANDOFF.md` and `projects//.context/CURRENT_TASK.json` -5. Continue work without re-explaining the project -6. In tool B, append more work, create the next handoff, and close the session - -## Interrupted-session recovery test - -1. Open a session and log at least one work entry -2. Do not close the session cleanly -3. Run `ctx.bat --project D:\Work\MyApp recover --session SESSION-REPLACE-ME --actor claude-recovery` -4. Confirm an emergency handoff and resume packet are written into the project workspace diff --git a/docs/TROUBLESHOOTING.md b/docs/TROUBLESHOOTING.md deleted file mode 100644 index 310b884..0000000 --- a/docs/TROUBLESHOOTING.md +++ /dev/null @@ -1,109 +0,0 @@ -# Troubleshooting Guide - -## Port `9300` already in use - -Check: - -```bat -netstat -ano | findstr :9300 -``` - -If another process owns the port, stop it or change the port only if you are willing to break the fixed `obsmcp` requirement. The safe default is to keep `9300` reserved for `obsmcp`. - -## Obsidian not running - -That is fine. The filesystem vault still syncs. Open Obsidian later and point it at either: - -- `projects//vault` for a single project -- `hub/vault` for the central dashboard - -## Obsidian Local REST API not available - -That is also fine. `obsmcp` does not depend on it. - -## Startup task fails - -Check: - -- Task Scheduler history for the `obsmcp` task -- `logs/startup.log` -- whether the install path moved after the task was created - -Reinstall with: - -```bat -uninstall_task_scheduler.bat -install_task_scheduler.bat -``` - -## Batch file path issues - -Keep the project in a stable location, preferably `C:\obsmcp`. If you move it, reinstall the Task Scheduler task. - -## Permission issues - -Make sure your user can write to: - -- `projects\\data\db` -- `logs` -- `projects\\.context` -- `projects\\vault` -- `hub\vault` - -## Sync issues or stale `.context` files - -Force a sync: - -```bat -ctx.bat --project D:\Work\MyApp sync -``` - -If files still look stale, inspect: - -- `projects//data/db/obsmcp.sqlite3` -- `logs/obsmcp-error.log` - -## Corrupted local DB - -Recovery approach: - -1. Stop `obsmcp` -2. Copy the newest file from `projects//data/backups` -3. Replace `projects//data/db/obsmcp.sqlite3` -4. Start `obsmcp` -5. Run `ctx.bat --project D:\Work\MyApp sync` - -## Resume or handoff is missing after an interrupted session - -Use the recovery flow: - -```bat -ctx.bat --project D:\Work\MyApp audit -ctx.bat --project D:\Work\MyApp resume -ctx.bat --project D:\Work\MyApp recover --session SESSION-REPLACE-ME --actor claude-recovery -``` - -This generates a best-effort emergency handoff and a fresh resume packet from the persisted state. - -## Server starts but health check fails - -Inspect: - -- `logs/startup.log` -- `logs/obsmcp.log` -- `logs/obsmcp-error.log` - -Then run: - -```bat -stop_obsmcp.bat -start_obsmcp.bat -``` - -If the hidden or detached launcher still does not keep the server alive, start `obsmcp` in its own console window: - -```powershell -Start-Process -FilePath cmd.exe -ArgumentList '/k','cd /d D:\Projects\obsmcp && .venv\Scripts\python.exe -m server.main' -``` - -That path is less elegant, but it is a reliable Windows fallback because the server stays attached to its own process window instead of the calling shell. diff --git a/docs/USAGE.md b/docs/USAGE.md deleted file mode 100644 index 73274e1..0000000 --- a/docs/USAGE.md +++ /dev/null @@ -1,368 +0,0 @@ -# Usage Guide - -## Daily operator flow - -1. Start `obsmcp` -2. Register the project repo if it has not been seen before -3. Open or resolve the centralized project workspace -4. Open a tracked session with `ctx` or MCP `session_open` -5. Create or select the current task with `ctx` -6. Use semantic lookups for targeted understanding before rereading large files -7. Log work as you go -8. Heartbeat long sessions -9. Record blockers and decisions when they happen -10. Create a handoff before switching models or stopping -11. Close the session and let the next tool read the project workspace `.context`, session folder, or query MCP - -## Automatic vs manual continuity behavior - -What is automatic now: - -- opening the same repo in another IDE or plugin generally resolves to the same `obsmcp` workspace -- `obsmcp` can infer project routing from `project_path`, `repo_path`, repo bridge files, and absolute file paths passed in tool arguments -- `obsmcp` can also infer project routing from `session_id`, `task_id`, `cwd`, and the nearest repo root when the client starts inside a project -- plugins can now call `resolve_active_project` with IDE metadata such as `cwd`, `active_file`, `workspace_folders`, `open_files`, or `env_variables` before their first write -- `session_open` defaults to `resume_strategy=auto`, so reopening the same actor/client/project usually resumes the recent open session instead of creating another one -- `session_open` now derives a readable `session_label` and stable `workstream_key`, or accepts them directly from the client -- `session_open` now normalizes client/model identity values and blocks unsafe auto-resume when the incoming request conflicts with the candidate session -- `session_close` now auto-enriches handoffs with relevant files, task state, and recommended semantic lookups even when only a short summary is provided - -What is still client-dependent: - -- whether the tool actually performs MCP write-back during work -- whether the tool updates relevant files or handoffs without being prompted -- whether the client includes a project hint on the first continuity-sensitive MCP call when it is not running inside the repo - -When a client does not write reliably, use `ctx.bat` as the fallback bridge. -When a client does not provide any usable project hint, `obsmcp` now rejects continuity-sensitive MCP calls with a clear error instead of silently writing into the default project. - -## Recommended startup guardrail flow - -Use this sequence before meaningful work: - -1. `ctx.bat --project D:\Work\myapp status` -2. `ctx.bat --project D:\Work\myapp preflight --actor codex --initial-request "..." --goal "..."` -3. `ctx.bat --project D:\Work\myapp resume-board` -4. Create or select the task -5. Open the session with a label and workstream when possible - -This catches the most common continuity problems before the model starts: - -- stale or abandoned sessions -- current task already marked done -- latest handoff belonging to another task -- substantial session startup with no task attached -- unsafe auto-resume candidates - -## Project workspace examples - -Register a repo: - -```bat -ctx.bat project register --repo D:\Work\myapp --name "My App" --tags python,fastapi -``` - -Inspect the centralized workspace paths: - -```bat -ctx.bat project paths --repo D:\Work\myapp -``` - -Migrate older repo-local `.context` / `obsidian\vault` content: - -```bat -ctx.bat project migrate --repo D:\Work\myapp -``` - -Generate a resume packet for the next model: - -```bat -ctx.bat --project D:\Work\myapp resume -``` - -Generate cached tiered context profiles: - -```bat -ctx.bat --project D:\Work\myapp compact --profile fast --max-tokens 1200 -ctx.bat --project D:\Work\myapp compact --profile balanced --max-tokens 2500 -ctx.bat --project D:\Work\myapp compact --profile deep --max-tokens 4500 --daily-notes -``` - -Generate a delta view since the latest handoff or a specific session: - -```bat -ctx.bat --project D:\Work\myapp delta -ctx.bat --project D:\Work\myapp delta --session SESSION-REPLACE-ME -ctx.bat --project D:\Work\myapp delta --handoff 42 -``` - -Queue a background Code Atlas scan and wait for it: - -```bat -ctx.bat --project D:\Work\myapp atlas generate --background -ctx.bat --project D:\Work\myapp atlas jobs -ctx.bat --project D:\Work\myapp atlas wait SCAN-REPLACE-ME --wait-seconds 60 -``` - -Recover an interrupted session: - -```bat -ctx.bat --project D:\Work\myapp recover --session SESSION-REPLACE-ME --actor claude-recovery -``` - -Refresh the central hub vault: - -```bat -ctx.bat hub sync -``` - -## Common CLI examples - -Create a task: - -```bat -ctx.bat --project D:\Work\myapp task create "Implement auth cache" --description "Add a local token cache for provider adapters" --files server/main.py,server/service.py -``` - -Set current task: - -```bat -ctx.bat --project D:\Work\myapp start TASK-12345678-implement-auth -``` - -Log work: - -```bat -ctx.bat --project D:\Work\myapp log "Added cache invalidation path" --task TASK-12345678-implement-auth --files server/service.py,tests/test_service.py -``` - -Describe a module: - -```bat -ctx.bat --project D:\Work\myapp describe module server\service.py -``` - -Describe a symbol: - -```bat -ctx.bat --project D:\Work\myapp describe symbol generate_resume_packet --module server\service.py --type function -``` - -Search semantic knowledge: - -```bat -ctx.bat --project D:\Work\myapp knowledge search "resume packet" -``` - -Open a session: - -```bat -ctx.bat session open --actor codex --client vscode-codex --model gpt-5 --project-path D:\Work\myapp --initial-request "Understand the codebase and continue the current task" --goal "Preserve continuity for the next model" -``` - -Open a named session inside a stable workstream: - -```bat -ctx.bat session open --actor claude-code --client claude-code-vscode --model claude-opus-4-6 --project-path D:\Work\myapp --task TASK-12345678-docs --label "Managing Director Email" --workstream managing-director-email --initial-request "This task is for the managing director's email." --goal "Draft and finalize the message" -``` - -Force a brand-new session instead of auto-resuming: - -```bat -ctx.bat session open --actor codex --client vscode-codex --model gpt-5 --project-path D:\Work\myapp --resume-strategy new -``` - -Run startup safety checks before opening or resuming: - -```bat -ctx.bat --project D:\Work\myapp preflight --actor codex --initial-request "Create the ERP documentation" --goal "Write the beginner guide" -``` - -Show the startup resume board: - -```bat -ctx.bat --project D:\Work\myapp resume-board -``` - -Check client/server compatibility: - -```bat -ctx.bat compat --client claude-code --model opus-4.6 --client-api-version 2026.04.14 --client-tool-schema-version 2 -``` - -Resume a specific previous session directly: - -```bat -ctx.bat session open --actor codex --client vscode-codex --model gpt-5 --project-path D:\Work\myapp --resume-strategy resume --resume-session-id SESSION-REPLACE-ME -``` - -Heartbeat a session: - -```bat -ctx.bat session heartbeat SESSION-REPLACE-ME --actor codex --note "Still tracing the auth path" --files server/service.py,server/store.py --create-work-log -``` - -Log a blocker: - -```bat -curl -X POST http://127.0.0.1:9300/mcp ^ - -H "Content-Type: application/json" ^ - -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"log_blocker\",\"arguments\":{\"title\":\"Missing API contract\",\"description\":\"Need final response shape from upstream client\",\"task_id\":\"TASK-12345678-implement-auth\",\"actor\":\"codex\"}}}" -``` - -Create a handoff: - -```bat -ctx.bat handoff --summary "Cache path is in place; tests still need edge-case coverage." --next-steps "Add concurrency tests; verify stale token eviction." --open-questions "Should cache be shared across workspaces?" --to "claude-code" -``` - -Close the session: - -```bat -ctx.bat session close SESSION-REPLACE-ME --actor codex --summary "Completed cache implementation and left handoff." --handoff-summary "Cache logic is in place and synced." -``` - -Audit continuity: - -```bat -ctx.bat --project D:\Work\myapp audit -``` - -Verify a reset actually left the workspace clean: - -```bat -curl -X POST http://127.0.0.1:9300/mcp ^ - -H "Content-Type: application/json" ^ - -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"reset_project\",\"arguments\":{\"scope\":\"full\",\"actor\":\"codex\",\"project_path\":\"D:\\Work\\myapp\"}}}" -``` - -The `reset_project` response now includes `post_reset_snapshot` so you can immediately verify: - -- `current_task` -- `active_tasks` -- `latest_handoff` -- `recent_work` -- `active_sessions` - -## MCP usage pattern - -The recommended read order for any MCP-capable client is: - -1. `get_project_status_snapshot` -2. `get_current_task` -3. `get_latest_handoff` -4. `get_blockers` -5. `generate_context_profile(profile="fast"|"balanced")` -6. `generate_delta_context` -7. `describe_module` / `describe_symbol` / `describe_feature` when you need targeted semantic understanding - -Recommended profile usage: - -- `fast`: lowest-latency startup, ideal for small edits or quick continuation -- `balanced`: default day-to-day context for normal coding sessions -- `deep`: more history, dependencies, and notes for debugging or architecture work -- `handoff`: same continuity surface, but biased toward transition quality -- `recovery`: same continuity surface, but includes audit-heavy recovery cues - -## Output response policy - -The `output_compression` block in `config/obsmcp.json` controls output-token behavior without changing continuity or context generation. - -Modes: - -- `off`: disable concise-response policy -- `prompt_only`: append a concise-response contract where `obsmcp` emits prompt text -- `gateway_enforced`: apply a stricter response contract where `obsmcp` directly controls model generation - -Current generation surfaces: - -- `generate_startup_prompt_template` -- LLM-backed semantic descriptions such as `describe_module`, `describe_symbol`, and `describe_feature` when they use the OpusMax text provider - -Inspect the effective policy for a task or operation: - -```bat -curl -X POST http://127.0.0.1:9300/mcp ^ - -H "Content-Type: application/json" ^ - -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"get_output_response_policy\",\"arguments\":{\"operation_kind\":\"review\",\"project_path\":\"D:\\Work\\myapp\"}}}" -``` - -Rollout order: - -1. start with `prompt_only` -2. validate output quality for your real tasks -3. move selected tasks to `gateway_enforced` -4. keep dangerous/security-sensitive flows on safety bypass or `off` - -For large repos, prefer this scan workflow: - -1. call `scan_codebase` -2. if the response status is `queued` or `running`, poll `get_scan_job` -3. optionally call `wait_for_scan_job` when the client supports longer polling windows -4. continue with `get_code_atlas_status`, `describe_module`, `describe_symbol`, or `search_code_knowledge` after the scan completes - -The recommended write pattern is: - -1. `session_open` -2. `log_work` -3. `log_decision` or `log_blocker` when needed -4. `session_heartbeat` during long work -5. `create_handoff` before exit or model switch -6. `session_close` - -Recommended first-call pattern for IDE clients and plugins: - -1. call `resolve_active_project` with `project_path` if known -2. otherwise pass `cwd`, `active_file`, `workspace_folders`, `open_files`, or IDE env hints -3. if the response is resolved, reuse the returned `project_path` for later calls -4. if the response says `requires_registration`, retry with `auto_register=true` or register explicitly -5. if the response is unresolved, ask the user for the project or wait until the client has a usable IDE hint - -Notes: - -- `session_open` now prefers reusing a recent matching open session for the same actor/client/project -- explicit `session_label` and `workstream_key` are recommended whenever the client can infer a named workstream from the user's prompt -- `get_startup_preflight` and `get_resume_board` are the preferred startup reads for IDE clients deciding between resume vs new work -- if a substantial request starts without `task_id`, `obsmcp` warns instead of silently treating the session as untracked work -- `session_close` will generate a richer handoff automatically if you do not provide every handoff field manually -- `bootstrap_default_project_on_startup=false` is now the recommended default, so the server starts empty and waits for an explicit project resolution instead of recreating a default project workspace during boot -- if a client only passes absolute file paths, `obsmcp` can often still route the write to the correct project workspace -- if the client is inside the repo, `session_open` and other continuity tools can route from `cwd` automatically -- if there is no reliable project signal at all, continuity-sensitive MCP calls fail and ask the client to pass `project_path` or another project hint first -- `sync_context_files` now also refreshes `.context/HOT_CONTEXT.md`, `.context/BALANCED_CONTEXT.md`, `.context/DEEP_CONTEXT.md`, and `.context/DELTA_CONTEXT.md` - -## Multi-tool continuity pattern - -### VS Code + Codex - -- start with `.context/PROJECT_CONTEXT.md` -- read `.context/CURRENT_TASK.json` -- query MCP if direct integration is available -- update state with `ctx.bat` or MCP tools - -### VS Code + Claude-style agent - -- open `.context` first -- read `AGENTS.md` and `CLAUDE.md` -- use `ctx.bat` when MCP is not directly available -- create a handoff before ending the session - -### Warp.dev - -- use `ctx.bat status`, `ctx.bat current`, and `ctx.bat log` -- paste the output of `ctx.bat status`, `generate_compact_context`, or `ctx.bat knowledge search ...` into the chat when needed - -### Cursor or similar IDE agents - -- point the agent to `.context` -- better: point it to the centralized project workspace `.context` -- if MCP config is supported, target `http://127.0.0.1:9300/mcp` -- otherwise use `ctx.bat` plus manual prompt injection - -### Manual-only tools - -- paste `.context/PROJECT_CONTEXT.md` -- paste `.context/HANDOFF.md` -- paste `.context/CURRENT_TASK.json` -- paste `master prompt.md` -- instruct the model to preserve continuity and write a new handoff before stopping diff --git a/docs/assets/obsmcp-overview.svg b/docs/assets/obsmcp-overview.svg deleted file mode 100644 index bd91bd0..0000000 --- a/docs/assets/obsmcp-overview.svg +++ /dev/null @@ -1,42 +0,0 @@ - - obsmcp overview - Overview graphic showing obsmcp as a continuity, context, and code understanding layer for AI development. - - - - - - - - - - - - - obsmcp - Project continuity + task memory + semantic code understanding for MCP-native AI workflows - - - - Continuity Layer - Tasks, handoffs, blockers, - sessions, resume safety, - startup preflight, resume board. - - - Context Engine - Fast, balanced, deep, delta, - retrieval context, progressive - loading, token-aware startup. - - - Code Intelligence - Code Atlas, semantic search, - module/symbol descriptions, - related-symbol expansion. - - - Why developers use it - One project-scoped memory layer for Codex, Claude Code, Cursor, Warp, and custom MCP clients. - Safer restarts, less prompt replay, better token discipline, and auditable AI work over time. - diff --git a/docs/assets/obsmcp-roadmap.svg b/docs/assets/obsmcp-roadmap.svg deleted file mode 100644 index 944c657..0000000 --- a/docs/assets/obsmcp-roadmap.svg +++ /dev/null @@ -1,25 +0,0 @@ - - obsmcp next roadmap - Roadmap card showing recommended next improvements for VS Code startup integration and output-token strategy. - - Next Recommended Commit - Improve VS Code startup integration and make output-token strategy easier to adopt. - - - Track A: VS Code Integration - 1. Resolve project before continuity reads - 2. Run startup preflight automatically - 3. Show resume board before resuming work - 4. Pass session labels + workstreams explicitly - 5. Prefer new sessions for unrelated prompts - Outcome: safer startup and cleaner session history - - - Track B: Output Strategy - 1. Expose recommended output mode helpers - 2. Add clearer task-type presets - 3. Show token savings in startup dashboards - 4. Expand docs for off / prompt_only / enforced - 5. Keep continuity and output policy clearly separate - Outcome: easier rollout of real output-token savings - diff --git a/frontend/.eslintrc.cjs b/frontend/.eslintrc.cjs new file mode 100644 index 0000000..40dc4da --- /dev/null +++ b/frontend/.eslintrc.cjs @@ -0,0 +1,15 @@ +module.exports = { + root: true, + env: { browser: true, es2022: true }, + parser: '@typescript-eslint/parser', + parserOptions: { ecmaVersion: 2022, sourceType: 'module', ecmaFeatures: { jsx: true } }, + plugins: ['@typescript-eslint', 'react-hooks', 'react-refresh'], + extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'], + rules: { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@typescript-eslint/no-explicit-any': 'off', + }, + ignorePatterns: ['dist', 'node_modules', '.eslintrc.cjs'], +}; diff --git a/frontend/.gitignore b/frontend/.gitignore new file mode 100644 index 0000000..acb8340 --- /dev/null +++ b/frontend/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +dist/ +.vite/ +*.tsbuildinfo + +# tsc-emitted files (noEmit is set but keep these ignored defensively) +src/**/*.js +src/**/*.d.ts diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..5b01aa1 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + + OBSMCP Dashboard + + +
+ + + diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..9244c12 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,5025 @@ +{ + "name": "obsmcp-frontend", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "obsmcp-frontend", + "version": "0.1.0", + "dependencies": { + "@tanstack/react-query": "^5.51.11", + "lucide-react": "^0.414.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.25.1", + "reactflow": "^11.11.4", + "recharts": "^2.12.7", + "zustand": "^4.5.4" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.17.0", + "@typescript-eslint/parser": "^7.17.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.9", + "postcss": "^8.4.40", + "tailwindcss": "^3.4.7", + "typescript": "^5.5.4", + "vite": "^5.3.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "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/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@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==", + "dev": true, + "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==", + "dev": true, + "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-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "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.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@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/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/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/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/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-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/@remix-run/router": { + "version": "1.23.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.2.tgz", + "integrity": "sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "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.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tanstack/query-core": { + "version": "5.99.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.99.2.tgz", + "integrity": "sha512-1HunU0bXVsR1ZJMZbcOPE6VtaBJxsW809RE9xPe4Gz7MlB0GWwQvuTPhMoEmQ/hIzFKJ/DWAuttIe7BOaWx0tA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.99.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.99.2.tgz", + "integrity": "sha512-vM91UEe45QUS9ED6OklsVL15i8qKcRqNwpWzPTVWvRPRSEgDudDgHpvyTjcdlwHcrKNa80T+xXYcchT2noPnZA==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.99.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/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==", + "dev": true, + "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/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "18.3.7", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", + "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^18.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz", + "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/type-utils": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^7.0.0", + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz", + "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz", + "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz", + "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/typescript-estree": "7.18.0", + "@typescript-eslint/utils": "7.18.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz", + "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz", + "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/visitor-keys": "7.18.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^1.3.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz", + "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "7.18.0", + "@typescript-eslint/types": "7.18.0", + "@typescript-eslint/typescript-estree": "7.18.0" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.56.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz", + "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "7.18.0", + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-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==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/autoprefixer": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz", + "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.2", + "caniuse-lite": "^1.0.30001787", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.20", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.20.tgz", + "integrity": "sha512-1AaXxEPfXT+GvTBJFuy4yXVHWJBXa4OdbIebGN/wX5DlsIkU0+wzGnd2lOzokSk51d5LUmqjgBLRLlypLUqInQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001788", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001788.tgz", + "integrity": "sha512-6q8HFp+lOQtcf7wBK+uEenxymVWkGKkjFpCvw5W25cmMwEDU45p1xQFBQv8JDlMMry7eNxyBaR+qxgmTUZkIRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/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/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/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/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", + "dev": true, + "license": "ISC" + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", + "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + } + }, + "node_modules/eslint-plugin-react-refresh": { + "version": "0.4.26", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", + "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=8.40" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.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==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", + "dev": true, + "license": "ISC" + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true, + "license": "MIT" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "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-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.414.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.414.0.tgz", + "integrity": "sha512-Krr/MHg9AWoJc52qx8hyJ64X9++JNfS1wjaJviLM1EP/68VNB7Tv0VMldLCB1aUe6Ka9QxURPhQm/eB6cqOM3A==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/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/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", + "integrity": "sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.30.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.3.tgz", + "integrity": "sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.23.2", + "react-router": "6.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.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==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "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.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "license": "MIT" + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/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/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", + "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "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/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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 + } + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..d814097 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,38 @@ +{ + "name": "obsmcp-frontend", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "typecheck": "tsc --noEmit", + "lint": "eslint . --ext .ts,.tsx", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.51.11", + "lucide-react": "^0.414.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.25.1", + "reactflow": "^11.11.4", + "recharts": "^2.12.7", + "zustand": "^4.5.4" + }, + "devDependencies": { + "@types/react": "^18.3.3", + "@types/react-dom": "^18.3.0", + "@typescript-eslint/eslint-plugin": "^7.17.0", + "@typescript-eslint/parser": "^7.17.0", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.19", + "eslint": "^8.57.0", + "eslint-plugin-react-hooks": "^4.6.2", + "eslint-plugin-react-refresh": "^0.4.9", + "postcss": "^8.4.40", + "tailwindcss": "^3.4.7", + "typescript": "^5.5.4", + "vite": "^5.3.5" + } +} diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..9915a4a --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,44 @@ +import { useEffect } from 'react'; +import { Route, Routes } from 'react-router-dom'; +import { useQueryClient } from '@tanstack/react-query'; + +import Layout from './components/Layout'; +import Dashboard from './pages/Dashboard'; +import Tasks from './pages/Tasks'; +import Sessions from './pages/Sessions'; +import Blockers from './pages/Blockers'; +import Decisions from './pages/Decisions'; +import WorkLogs from './pages/WorkLogs'; +import CodeAtlas from './pages/CodeAtlas'; +import KnowledgeGraph from './pages/KnowledgeGraph'; +import PerformanceLogs from './pages/PerformanceLogs'; +import SettingsPage from './pages/Settings'; +import NotFound from './pages/NotFound'; +import { eventBus } from './events/EventBus'; + +export default function App(): JSX.Element { + const qc = useQueryClient(); + useEffect(() => { + eventBus.attachQueryClient(qc); + eventBus.connect(); + return () => eventBus.disconnect(); + }, [qc]); + + return ( + + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + ); +} diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts new file mode 100644 index 0000000..23c7122 --- /dev/null +++ b/frontend/src/api/client.ts @@ -0,0 +1,63 @@ +const TOKEN_KEY = 'obsmcp:token'; + +export function getApiToken(): string | null { + try { + return localStorage.getItem(TOKEN_KEY); + } catch { + return null; + } +} + +export function setApiToken(token: string): void { + try { + localStorage.setItem(TOKEN_KEY, token); + } catch { + /* noop */ + } +} + +export function clearApiToken(): void { + try { + localStorage.removeItem(TOKEN_KEY); + } catch { + /* noop */ + } +} + +function authHeaders(): Record { + const token = getApiToken(); + return token ? { Authorization: `Bearer ${token}` } : {}; +} + +async function handle(res: Response): Promise { + if (!res.ok) { + const detail = await res.text(); + throw new Error(`${res.status} ${res.statusText}: ${detail}`); + } + if (res.status === 204) return undefined as T; + return (await res.json()) as T; +} + +const base = ''; + +export const api = { + get: (path: string) => + fetch(`${base}${path}`, { headers: authHeaders() }).then((r) => handle(r)), + post: (path: string, body: unknown) => + fetch(`${base}${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', ...authHeaders() }, + body: JSON.stringify(body ?? {}), + }).then((r) => handle(r)), + put: (path: string, body: unknown) => + fetch(`${base}${path}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json', ...authHeaders() }, + body: JSON.stringify(body ?? {}), + }).then((r) => handle(r)), + del: (path: string) => + fetch(`${base}${path}`, { + method: 'DELETE', + headers: authHeaders(), + }).then((r) => handle(r)), +}; diff --git a/frontend/src/api/types.ts b/frontend/src/api/types.ts new file mode 100644 index 0000000..08c7af5 --- /dev/null +++ b/frontend/src/api/types.ts @@ -0,0 +1,125 @@ +export interface Task { + id: string; + project_id: string | null; + title: string; + description: string | null; + status: 'open' | 'in_progress' | 'done' | 'blocked'; + priority: 'low' | 'medium' | 'high' | 'urgent'; + tags: string[] | null; + created_at: string; + updated_at: string; +} + +export interface Session { + id: string; + project_id: string | null; + agent_id: string; + started_at: string; + ended_at: string | null; + duration_seconds: number | null; + context: string | null; +} + +export interface Blocker { + id: string; + project_id: string | null; + agent_id: string | null; + description: string; + severity: 'low' | 'medium' | 'high' | 'critical'; + status: 'active' | 'resolved'; + resolved_at: string | null; + resolution: string | null; + created_at: string; +} + +export interface Decision { + id: string; + project_id: string | null; + agent_id: string | null; + decision: string; + context: string | null; + outcome: string | null; + tags: string[] | null; + created_at: string; +} + +export interface WorkLog { + id: string; + project_id: string | null; + session_id: string | null; + agent_id: string | null; + description: string; + hours: number | null; + tags: string[] | null; + created_at: string; +} + +export interface CodeAtlasScan { + id: string; + project_id: string | null; + agent_id: string | null; + status: 'pending' | 'running' | 'completed' | 'failed'; + total_files: number; + scanned_files: number; + started_at: string; + completed_at: string | null; + error_message: string | null; +} + +export interface CodeAtlasFile { + id: string; + scan_id: string; + project_id: string | null; + file_path: string; + language: string | null; + functions_count: number; + imports: string[]; + exports: string[]; + semantic_description: string | null; + scanned_at: string; +} + +export interface KnowledgeNode { + id: string; + project_id: string | null; + agent_id: string | null; + node_type: string; + name: string; + description: string | null; + metadata: Record; + created_at: string; +} + +export interface KnowledgeEdge { + id: string; + project_id: string | null; + from_node_id: string; + to_node_id: string; + edge_type: string; + weight: number; + metadata: Record; + created_at: string; +} + +export interface PerformanceLog { + id: string; + project_id: string | null; + agent_id: string | null; + session_id: string | null; + metric_name: string; + metric_value: number; + unit: string | null; + tags: Record; + logged_at: string; +} + +export interface Stats { + tasks: { total: number; open: number; in_progress: number; blocked: number; done: number }; + sessions: { total: number; active: number }; + blockers: { active: number; resolved: number }; + decisions: number; + work_logs: number; + nodes: number; + edges: number; + agents: number; +} diff --git a/frontend/src/components/EmptyState.tsx b/frontend/src/components/EmptyState.tsx new file mode 100644 index 0000000..76b64ea --- /dev/null +++ b/frontend/src/components/EmptyState.tsx @@ -0,0 +1,17 @@ +interface Props { + title: string; + description?: string; + action?: React.ReactNode; +} + +export default function EmptyState({ title, description, action }: Props): JSX.Element { + return ( +
+
+

{title}

+ {description &&

{description}

} + {action &&
{action}
} +
+
+ ); +} diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx new file mode 100644 index 0000000..80c2211 --- /dev/null +++ b/frontend/src/components/Layout.tsx @@ -0,0 +1,79 @@ +import { NavLink, Outlet } from 'react-router-dom'; +import { + Activity, + AlertTriangle, + BookOpenText, + ClipboardList, + Gauge, + Layers, + LineChart, + ListChecks, + Network, + Settings, + Wifi, + WifiOff, + type LucideIcon, +} from 'lucide-react'; +import { useConnectionStatus } from '../hooks/useConnectionStatus'; + +interface NavItem { + to: string; + label: string; + icon: LucideIcon; +} + +const NAV: NavItem[] = [ + { to: '/', label: 'Dashboard', icon: Gauge }, + { to: '/tasks', label: 'Tasks', icon: ListChecks }, + { to: '/sessions', label: 'Sessions', icon: Activity }, + { to: '/blockers', label: 'Blockers', icon: AlertTriangle }, + { to: '/decisions', label: 'Decisions', icon: BookOpenText }, + { to: '/work-logs', label: 'Work Logs', icon: ClipboardList }, + { to: '/code-atlas', label: 'Code Atlas', icon: Layers }, + { to: '/graph', label: 'Knowledge Graph', icon: Network }, + { to: '/logs', label: 'Performance', icon: LineChart }, + { to: '/settings', label: 'Settings', icon: Settings }, +]; + +export default function Layout(): JSX.Element { + const connected = useConnectionStatus(); + return ( +
+ +
+ +
+
+ ); +} diff --git a/frontend/src/components/PageHeader.tsx b/frontend/src/components/PageHeader.tsx new file mode 100644 index 0000000..a2d22c6 --- /dev/null +++ b/frontend/src/components/PageHeader.tsx @@ -0,0 +1,17 @@ +interface Props { + title: string; + description?: string; + actions?: React.ReactNode; +} + +export default function PageHeader({ title, description, actions }: Props): JSX.Element { + return ( +
+
+

{title}

+ {description &&

{description}

} +
+ {actions &&
{actions}
} +
+ ); +} diff --git a/frontend/src/events/EventBus.ts b/frontend/src/events/EventBus.ts new file mode 100644 index 0000000..03288c3 --- /dev/null +++ b/frontend/src/events/EventBus.ts @@ -0,0 +1,160 @@ +import { QueryClient } from '@tanstack/react-query'; +import { getApiToken } from '../api/client'; + +export type EventType = + | 'connected' + | 'heartbeat' + | 'task_created' + | 'task_updated' + | 'task_deleted' + | 'session_opened' + | 'session_closed' + | 'session_heartbeat' + | 'blocker_logged' + | 'blocker_resolved' + | 'blocker_deleted' + | 'decision_logged' + | 'decision_updated' + | 'decision_deleted' + | 'work_logged' + | 'work_log_updated' + | 'work_log_deleted' + | 'scan_started' + | 'scan_progress' + | 'scan_completed' + | 'node_created' + | 'node_updated' + | 'node_deleted' + | 'nodes_bulk_created' + | 'edge_created' + | 'edge_deleted' + | 'edges_bulk_created' + | 'perf_log_received' + | 'agent_connected' + | 'agent_disconnected'; + +export interface OBSMCPEvent { + type: EventType | string; + payload: Record; + timestamp: string; +} + +type Listener = (event: OBSMCPEvent) => void; + +const EVENT_TO_QUERY: Record = { + task_created: ['tasks', 'stats'], + task_updated: ['tasks', 'stats'], + task_deleted: ['tasks', 'stats'], + session_opened: ['sessions', 'stats'], + session_closed: ['sessions', 'stats'], + session_heartbeat: ['sessions'], + blocker_logged: ['blockers', 'stats'], + blocker_resolved: ['blockers', 'stats'], + blocker_deleted: ['blockers', 'stats'], + decision_logged: ['decisions', 'stats'], + decision_updated: ['decisions'], + decision_deleted: ['decisions', 'stats'], + work_logged: ['work-logs', 'stats'], + work_log_updated: ['work-logs'], + work_log_deleted: ['work-logs', 'stats'], + scan_started: ['code-atlas'], + scan_progress: ['code-atlas'], + scan_completed: ['code-atlas'], + node_created: ['knowledge-graph', 'stats'], + node_updated: ['knowledge-graph'], + node_deleted: ['knowledge-graph', 'stats'], + nodes_bulk_created: ['knowledge-graph', 'stats'], + edge_created: ['knowledge-graph', 'stats'], + edge_deleted: ['knowledge-graph', 'stats'], + edges_bulk_created: ['knowledge-graph', 'stats'], + perf_log_received: ['performance-logs'], + agent_connected: ['agents', 'stats'], + agent_disconnected: ['agents', 'stats'], +}; + +export class EventBus { + private listeners = new Set(); + private es: EventSource | null = null; + private reconnectTimer: number | null = null; + private queryClient: QueryClient | null = null; + public connected = false; + private statusListeners = new Set<(c: boolean) => void>(); + + attachQueryClient(qc: QueryClient): void { + this.queryClient = qc; + } + + connect(): void { + if (this.es) return; + const token = getApiToken(); + const url = token ? `/api/events?token=${encodeURIComponent(token)}` : '/api/events'; + const es = new EventSource(url); + this.es = es; + es.onopen = () => { + this.connected = true; + this.statusListeners.forEach((fn) => fn(true)); + }; + es.onerror = () => { + this.connected = false; + this.statusListeners.forEach((fn) => fn(false)); + es.close(); + this.es = null; + this.scheduleReconnect(); + }; + es.onmessage = (ev) => this.handleMessage(ev.data); + // Listen to all named events too. + const knownTypes: string[] = Object.keys(EVENT_TO_QUERY).concat(['connected', 'heartbeat']); + for (const t of knownTypes) { + es.addEventListener(t, (ev: MessageEvent) => this.handleMessage(ev.data)); + } + } + + disconnect(): void { + if (this.es) { + this.es.close(); + this.es = null; + } + if (this.reconnectTimer) { + window.clearTimeout(this.reconnectTimer); + this.reconnectTimer = null; + } + this.connected = false; + } + + subscribe(fn: Listener): () => void { + this.listeners.add(fn); + return () => this.listeners.delete(fn); + } + + onStatus(fn: (connected: boolean) => void): () => void { + this.statusListeners.add(fn); + return () => this.statusListeners.delete(fn); + } + + private handleMessage(raw: string): void { + if (!raw || raw.startsWith(':')) return; + let event: OBSMCPEvent; + try { + event = JSON.parse(raw); + } catch { + return; + } + this.listeners.forEach((l) => l(event)); + const keys = EVENT_TO_QUERY[event.type] ?? []; + if (this.queryClient) { + for (const k of keys) { + void this.queryClient.invalidateQueries({ queryKey: [k] }); + } + } + } + + private scheduleReconnect(): void { + if (this.reconnectTimer) return; + this.reconnectTimer = window.setTimeout(() => { + this.reconnectTimer = null; + this.connect(); + }, 3_000); + } +} + +export const eventBus = new EventBus(); diff --git a/frontend/src/hooks/useConnectionStatus.ts b/frontend/src/hooks/useConnectionStatus.ts new file mode 100644 index 0000000..d6caa5e --- /dev/null +++ b/frontend/src/hooks/useConnectionStatus.ts @@ -0,0 +1,8 @@ +import { useEffect, useState } from 'react'; +import { eventBus } from '../events/EventBus'; + +export function useConnectionStatus(): boolean { + const [connected, setConnected] = useState(eventBus.connected); + useEffect(() => eventBus.onStatus(setConnected), []); + return connected; +} diff --git a/frontend/src/hooks/useEvent.ts b/frontend/src/hooks/useEvent.ts new file mode 100644 index 0000000..a1f1927 --- /dev/null +++ b/frontend/src/hooks/useEvent.ts @@ -0,0 +1,6 @@ +import { useEffect } from 'react'; +import { eventBus, OBSMCPEvent } from '../events/EventBus'; + +export function useEvent(handler: (event: OBSMCPEvent) => void): void { + useEffect(() => eventBus.subscribe(handler), [handler]); +} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..017d0fe --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,25 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, body, #root { + height: 100%; +} + +@layer components { + .card { + @apply rounded-lg border border-slate-200 bg-white p-4 shadow-sm; + } + .btn { + @apply inline-flex items-center gap-1 rounded-md px-3 py-1.5 text-sm font-medium transition; + } + .btn-primary { + @apply btn bg-brand-600 text-white hover:bg-brand-700; + } + .btn-secondary { + @apply btn bg-slate-100 text-slate-800 hover:bg-slate-200; + } + .tag { + @apply inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium; + } +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..1bd1e81 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; + +import App from './App'; +import './index.css'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}); + +const root = document.getElementById('root'); +if (!root) throw new Error('Missing #root element'); + +ReactDOM.createRoot(root).render( + + + + + + + +); diff --git a/frontend/src/pages/Blockers.tsx b/frontend/src/pages/Blockers.tsx new file mode 100644 index 0000000..e0c8646 --- /dev/null +++ b/frontend/src/pages/Blockers.tsx @@ -0,0 +1,108 @@ +import { useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import PageHeader from '../components/PageHeader'; +import EmptyState from '../components/EmptyState'; +import { api } from '../api/client'; +import type { Blocker } from '../api/types'; + +export default function BlockersPage(): JSX.Element { + const qc = useQueryClient(); + const [description, setDescription] = useState(''); + const [severity, setSeverity] = useState('medium'); + const [resolution, setResolution] = useState>({}); + + const blockers = useQuery({ + queryKey: ['blockers'], + queryFn: () => api.get('/api/blockers'), + }); + const create = useMutation({ + mutationFn: (body: Partial) => api.post('/api/blockers', body), + onSuccess: () => qc.invalidateQueries({ queryKey: ['blockers'] }), + }); + const resolve = useMutation({ + mutationFn: ({ id, r }: { id: string; r: string }) => + api.put(`/api/blockers/${id}/resolve`, { resolution: r }), + onSuccess: () => qc.invalidateQueries({ queryKey: ['blockers'] }), + }); + + return ( + <> + +
{ + e.preventDefault(); + if (!description.trim()) return; + create.mutate({ description: description.trim(), severity }); + setDescription(''); + }} + > + setDescription(e.target.value)} + /> + + +
+ {blockers.data && blockers.data.length === 0 ? ( + + ) : ( +
+ {(blockers.data ?? []).map((b) => ( +
+
+

{b.description}

+ + {b.status} + +
+

+ severity: {b.severity} · logged {b.created_at} +

+ {b.status === 'active' ? ( +
+ setResolution((s) => ({ ...s, [b.id]: e.target.value }))} + /> + +
+ ) : ( +

Resolved: {b.resolution}

+ )} +
+ ))} +
+ )} + + ); +} diff --git a/frontend/src/pages/CodeAtlas.tsx b/frontend/src/pages/CodeAtlas.tsx new file mode 100644 index 0000000..f3ea3c6 --- /dev/null +++ b/frontend/src/pages/CodeAtlas.tsx @@ -0,0 +1,165 @@ +import { useMemo, useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { + Bar, + BarChart, + CartesianGrid, + Cell, + Legend, + Pie, + PieChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts'; +import { Play } from 'lucide-react'; + +import PageHeader from '../components/PageHeader'; +import EmptyState from '../components/EmptyState'; +import { api } from '../api/client'; +import type { CodeAtlasFile, CodeAtlasScan } from '../api/types'; + +const COLORS = ['#5b71ff', '#f97316', '#22c55e', '#ef4444', '#a855f7', '#eab308', '#06b6d4']; + +export default function CodeAtlasPage(): JSX.Element { + const qc = useQueryClient(); + const scans = useQuery({ + queryKey: ['code-atlas', 'scans'], + queryFn: () => api.get('/api/code-atlas'), + }); + const latestScan = scans.data?.[0]; + const filesQuery = useQuery<{ files: CodeAtlasFile[] }>({ + queryKey: ['code-atlas', 'files', latestScan?.id], + enabled: !!latestScan?.id, + queryFn: () => + api.get<{ files: CodeAtlasFile[] }>(`/api/code-atlas/scan/${latestScan!.id}/files?per_page=500`), + }); + const start = useMutation({ + mutationFn: () => api.post('/api/code-atlas/scan', {}), + onSuccess: () => qc.invalidateQueries({ queryKey: ['code-atlas'] }), + }); + + const files = filesQuery.data?.files ?? []; + const [selected, setSelected] = useState(null); + + const languageDistribution = useMemo(() => { + const counts: Record = {}; + for (const f of files) { + const key = f.language ?? 'unknown'; + counts[key] = (counts[key] ?? 0) + 1; + } + return Object.entries(counts).map(([name, value]) => ({ name, value })); + }, [files]); + + const topFunctions = useMemo( + () => + [...files] + .sort((a, b) => b.functions_count - a.functions_count) + .slice(0, 10) + .map((f) => ({ name: f.file_path.split('/').pop() ?? f.file_path, count: f.functions_count })), + [files], + ); + + return ( + <> + start.mutate()} + > + {start.isPending ? 'Starting…' : 'Run scan'} + + } + /> + {!latestScan ? ( + + ) : ( + <> +
+
+

Language distribution

+ + + + {languageDistribution.map((_, i) => ( + + ))} + + + + + +
+
+

Top files by function count

+ + + + + + + + + +
+
+ +
+
+

+ Files ({files.length}) +

+
    + {files.map((f) => ( +
  • setSelected(f)} + > +
    {f.file_path}
    +
    + {f.language} · {f.functions_count} fn +
    +
  • + ))} +
+
+
+ {selected ? ( + <> +

{selected.file_path}

+

+ {selected.language} · {selected.functions_count} functions +

+

+ {selected.semantic_description ?? 'No semantic description yet.'} +

+

Imports

+
    + {(selected.imports ?? []).map((imp) => ( +
  • + {imp} +
  • + ))} +
+ + ) : ( +

Select a file to see details.

+ )} +
+
+ + )} + + ); +} diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx new file mode 100644 index 0000000..ce6a4ad --- /dev/null +++ b/frontend/src/pages/Dashboard.tsx @@ -0,0 +1,113 @@ +import { useQuery } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; +import { + Activity, + AlertTriangle, + BookOpenText, + ClipboardList, + Layers, + ListChecks, + Network, + type LucideIcon, +} from 'lucide-react'; + +import PageHeader from '../components/PageHeader'; +import { api } from '../api/client'; +import type { Blocker, Session, Stats, Task } from '../api/types'; + +interface StatCard { + label: string; + value: string | number; + to: string; + icon: LucideIcon; +} + +export default function Dashboard(): JSX.Element { + const stats = useQuery({ + queryKey: ['stats'], + queryFn: () => api.get('/api/stats'), + }); + const recentTasks = useQuery({ + queryKey: ['tasks'], + queryFn: () => api.get('/api/tasks'), + }); + const activeSessions = useQuery({ + queryKey: ['sessions', { active: true }], + queryFn: () => api.get('/api/sessions?active=true'), + }); + const activeBlockers = useQuery({ + queryKey: ['blockers', { status: 'active' }], + queryFn: () => api.get('/api/blockers?status=active'), + }); + + const s = stats.data; + const cards: StatCard[] = [ + { label: 'Open tasks', value: s?.tasks.open ?? '—', to: '/tasks', icon: ListChecks }, + { label: 'Active sessions', value: s?.sessions.active ?? '—', to: '/sessions', icon: Activity }, + { label: 'Active blockers', value: s?.blockers.active ?? '—', to: '/blockers', icon: AlertTriangle }, + { label: 'Decisions', value: s?.decisions ?? '—', to: '/decisions', icon: BookOpenText }, + { label: 'Work logs', value: s?.work_logs ?? '—', to: '/work-logs', icon: ClipboardList }, + { label: 'Code Atlas nodes', value: s?.nodes ?? '—', to: '/graph', icon: Network }, + { label: 'Scanned files', value: s?.edges ?? '—', to: '/code-atlas', icon: Layers }, + ]; + + return ( + <> + +
+ {cards.map((c) => ( + +
+ +
+
{c.value}
+
{c.label}
+
+
+ + ))} +
+ +
+
+

Recent tasks

+ {(recentTasks.data ?? []).slice(0, 5).map((t) => ( +
+
{t.title}
+
+ {t.status} · {t.priority} +
+
+ ))} + {recentTasks.data && recentTasks.data.length === 0 && ( +

No tasks yet.

+ )} +
+
+

Active sessions

+ {(activeSessions.data ?? []).slice(0, 5).map((s2) => ( +
+
{s2.agent_id}
+
since {s2.started_at}
+
+ ))} + {activeSessions.data && activeSessions.data.length === 0 && ( +

No active sessions.

+ )} +
+
+

Active blockers

+ {(activeBlockers.data ?? []).slice(0, 5).map((b) => ( +
+
{b.description}
+
{b.severity}
+
+ ))} + {activeBlockers.data && activeBlockers.data.length === 0 && ( +

Nothing blocking.

+ )} +
+
+ + ); +} diff --git a/frontend/src/pages/Decisions.tsx b/frontend/src/pages/Decisions.tsx new file mode 100644 index 0000000..0d8d4b5 --- /dev/null +++ b/frontend/src/pages/Decisions.tsx @@ -0,0 +1,70 @@ +import { useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import PageHeader from '../components/PageHeader'; +import EmptyState from '../components/EmptyState'; +import { api } from '../api/client'; +import type { Decision } from '../api/types'; + +export default function DecisionsPage(): JSX.Element { + const qc = useQueryClient(); + const [decision, setDecision] = useState(''); + const [context, setContext] = useState(''); + const decisions = useQuery({ + queryKey: ['decisions'], + queryFn: () => api.get('/api/decisions'), + }); + const create = useMutation({ + mutationFn: (body: Partial) => api.post('/api/decisions', body), + onSuccess: () => { + qc.invalidateQueries({ queryKey: ['decisions'] }); + setDecision(''); + setContext(''); + }, + }); + + return ( + <> + +
{ + e.preventDefault(); + if (!decision.trim()) return; + create.mutate({ decision: decision.trim(), context: context.trim() || null }); + }} + className="card mb-4 space-y-2" + > + setDecision(e.target.value)} + placeholder="Decision title" + className="w-full rounded-md border border-slate-300 px-3 py-2 text-sm" + /> +