diff --git a/memory-viewer b/memory-viewer deleted file mode 160000 index 1b7f2aa..0000000 --- a/memory-viewer +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 1b7f2aa57a8be41ee66d93b714c4b416fb6b4dd8 diff --git a/memory-viewer/.dockerignore b/memory-viewer/.dockerignore new file mode 100644 index 0000000..e1fc743 --- /dev/null +++ b/memory-viewer/.dockerignore @@ -0,0 +1,16 @@ +node_modules +npm-debug.log +.git +.gitignore +README.zh-CN.md +CONTRIBUTING.md +LICENSE +docs +.env +.nyc_output +coverage +.DS_Store +*.log + +# Keep CHANGELOG.md - it's imported by src/components/Changelog.tsx +# CHANGELOG.md diff --git a/memory-viewer/.github/ISSUE_TEMPLATE/bug_report.md b/memory-viewer/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..85e7ee4 --- /dev/null +++ b/memory-viewer/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,28 @@ +--- +name: Bug Report +about: Report a bug to help us improve +title: "[Bug] " +labels: bug +--- + +## Describe the Bug +A clear and concise description of what the bug is. + +## Steps to Reproduce +1. Go to '...' +2. Click on '...' +3. See error + +## Expected Behavior +What you expected to happen. + +## Actual Behavior +What actually happened. + +## Environment +- OS: [e.g., macOS 15, Ubuntu 24.04] +- Node.js version: [e.g., 22.x] +- Browser: [e.g., Chrome 130] + +## Screenshots +If applicable, add screenshots. diff --git a/memory-viewer/.github/ISSUE_TEMPLATE/feature_request.md b/memory-viewer/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..063d3fa --- /dev/null +++ b/memory-viewer/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,18 @@ +--- +name: Feature Request +about: Suggest an idea for this project +title: "[Feature] " +labels: enhancement +--- + +## Problem +A clear description of the problem or need. E.g., "I'm always frustrated when..." + +## Proposed Solution +Describe the solution you'd like. + +## Alternatives Considered +Any alternative solutions or features you've considered. + +## Additional Context +Any other context, mockups, or screenshots. diff --git a/memory-viewer/.github/workflows/docker-build.yml b/memory-viewer/.github/workflows/docker-build.yml new file mode 100644 index 0000000..c70316e --- /dev/null +++ b/memory-viewer/.github/workflows/docker-build.yml @@ -0,0 +1,123 @@ +name: Build and Push Docker Image + +on: + push: + branches: [ main ] + tags: [ 'v*' ] + pull_request: + branches: [ main ] + workflow_dispatch: + inputs: + tag: + description: 'Image tag (default: latest)' + required: false + default: 'latest' + +env: + REGISTRY: ghcr.io + IMAGE_NAME: ${{ github.repository }} + +jobs: + build: + strategy: + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-latest + suffix: amd64 + - platform: linux/arm64 + runner: ubuntu-24.04-arm + suffix: arm64 + runs-on: ${{ matrix.runner }} + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Run Tests + run: npm test + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Container Registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch,suffix=-${{ matrix.suffix }} + type=ref,event=pr,suffix=-${{ matrix.suffix }} + type=semver,pattern={{version}},suffix=-${{ matrix.suffix }} + type=sha,prefix={{branch}}-,suffix=-${{ matrix.suffix }} + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=${{ matrix.suffix }} + cache-to: type=gha,mode=max,scope=${{ matrix.suffix }} + platforms: ${{ matrix.platform }} + + manifest: + needs: build + if: github.event_name != 'pull_request' + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Log in to Container Registry + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=sha,prefix={{branch}}- + type=raw,value=${{ inputs.tag }},enable=${{ github.event_name == 'workflow_dispatch' }} + + - name: Create and push multi-arch manifest + run: | + TAGS="${{ steps.meta.outputs.tags }}" + for TAG in $TAGS; do + echo "Creating manifest for $TAG" + docker manifest create "$TAG" \ + "${TAG}-amd64" \ + "${TAG}-arm64" || true + docker manifest push "$TAG" || true + done diff --git a/memory-viewer/.gitignore b/memory-viewer/.gitignore new file mode 100644 index 0000000..aeb92d2 --- /dev/null +++ b/memory-viewer/.gitignore @@ -0,0 +1,27 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules/ +dist/ +dist-ssr/ +*.local +.env +.env.* +server.log + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/memory-viewer/CHANGELOG.md b/memory-viewer/CHANGELOG.md new file mode 100644 index 0000000..825f2de --- /dev/null +++ b/memory-viewer/CHANGELOG.md @@ -0,0 +1,46 @@ +# Changelog + +All notable changes to Memory Viewer will be documented in this file. + +## [1.2.0] "Road Trip" - 2026-02-01 + +### Added +- **PWA support** — Installable as standalone app on mobile, desktop, and Tesla. Offline caching with service worker. +- **Hash-based routing** — URL updates to `#/file/` when opening files. Refresh restores active file, browser back/forward works. +- **Mermaid diagram rendering** — Fenced `mermaid` code blocks render as SVG diagrams with theme-aware styling. +- **Auto-refresh polling** — Files auto-poll every 10s as WebSocket fallback. Manual refresh button in toolbar. + +### Changed +- **Express → Hono migration** — Replaced Express + compression + cors with Hono (~14KB). Faster, lighter, better TypeScript support. +- **Monospace font fix** — Code blocks now enforce `var(--font-mono)` for proper CJK alignment in ASCII art. + +### Performance +- **Gzip compression** — Server-side compression middleware. +- **CodeMirror lazy loading** — First-paint gzip reduced from 535KB → 310KB. +- **Vendor chunk splitting** — Separate chunks for react, codemirror, markdown, icons. +- **Tesla/large screen optimization** — 24px base font, 56px touch targets, wider sidebar. + +### Fixed +- Code blocks without language tag now render properly with `
` instead of inline code.
+
+## [1.1.0] - 2026-01-31
+
+### Added
+- **Optimistic locking for concurrent edits** — When saving a file that was modified on disk while you were editing, a conflict dialog appears with options to Overwrite, Reload, or Cancel. Prevents accidental data loss when the agent writes to a file during human editing.
+- **Changelog page** — In-app changelog accessible from the sidebar footer.
+
+## [1.0.0] - 2026-01-30
+
+### Added
+- **Multi-bot remote connections** — Connect to multiple OpenClaw agent workspaces from a single UI. One-click setup via Gateway API bootstrap.
+- **Markdown editor** — In-browser editing with Ctrl+S to save.
+- **Full-text search** — Search across all memory files instantly (Ctrl+K).
+- **System dashboard** — Server uptime, memory usage, load averages, today's memory summary, recent files, and monthly activity chart.
+- **Live reload** — Files auto-refresh when changed on disk via WebSocket.
+- **Dark/light theme** — Toggle between dark and light themes.
+- **Sensitive content masking** — Blur/reveal sensitive text with one click.
+- **i18n support** — English and Chinese interface.
+- **Syntax highlighting** — Code blocks with Prism.js, copy button, line numbers.
+- **File tree sidebar** — Navigate all `.md` files in a collapsible tree with grouping.
+- **Responsive design** — Works on mobile with a slide-out sidebar.
+- **Bot identity display** — Reads from SOUL.md / IDENTITY.md.
diff --git a/memory-viewer/CONTRIBUTING.md b/memory-viewer/CONTRIBUTING.md
new file mode 100644
index 0000000..a4a8001
--- /dev/null
+++ b/memory-viewer/CONTRIBUTING.md
@@ -0,0 +1,52 @@
+# Contributing to Memory Viewer
+
+Thanks for your interest in contributing! Here's how to get started.
+
+## Development Setup
+
+1. **Prerequisites:** Node.js >= 18, npm
+2. Clone and install:
+   ```bash
+   git clone https://github.com/silicondawn/memory-viewer.git
+   cd memory-viewer
+   npm install
+   ```
+3. Start the dev server:
+   ```bash
+   npm run dev
+   ```
+   This runs both the API server and Vite dev server concurrently.
+
+## Submitting Pull Requests
+
+1. Fork the repo and create a feature branch from `master`:
+   ```bash
+   git checkout -b feat/my-feature
+   ```
+2. Make your changes and ensure they pass type checking:
+   ```bash
+   npm run typecheck
+   ```
+3. Commit with a clear message following [Conventional Commits](https://www.conventionalcommits.org/):
+   - `feat:` for new features
+   - `fix:` for bug fixes
+   - `chore:` for maintenance tasks
+   - `docs:` for documentation changes
+4. Push and open a PR against `master`.
+
+## Code Style
+
+- **TypeScript** — All new code should be written in TypeScript.
+- **React** — Functional components with hooks.
+- **Tailwind CSS v4** — Use utility classes; avoid custom CSS when possible.
+- **Formatting** — Keep lines reasonable (~100 chars). No trailing whitespace.
+- Run `npm run typecheck` before committing.
+
+## Reporting Issues
+
+- Use the [Bug Report](https://github.com/silicondawn/memory-viewer/issues/new?template=bug_report.md) or [Feature Request](https://github.com/silicondawn/memory-viewer/issues/new?template=feature_request.md) templates.
+- Include steps to reproduce, expected vs actual behavior, and your environment (OS, Node version, browser).
+
+## License
+
+By contributing, you agree that your contributions will be licensed under the [MIT License](./LICENSE).
diff --git a/memory-viewer/Dockerfile b/memory-viewer/Dockerfile
new file mode 100644
index 0000000..b4ed061
--- /dev/null
+++ b/memory-viewer/Dockerfile
@@ -0,0 +1,61 @@
+# Multi-stage build for production
+FROM node:20-alpine AS builder
+
+WORKDIR /app
+
+# Copy package files first for better layer caching
+COPY package*.json ./
+RUN npm ci
+
+# Copy source code
+COPY . .
+
+# Build frontend
+RUN npm run build
+
+# Fix ServerWebSocket type issue by replacing with any type
+RUN sed -i 's/import type { ServerWebSocket } from "@hono\/node-ws";/type ServerWebSocket = any;/' server/index.ts
+
+# Compile TypeScript server to JavaScript
+RUN npx tsc --project tsconfig.server.json
+
+# Production stage
+FROM node:20-alpine AS production
+
+WORKDIR /app
+
+# Create non-root user for security
+RUN addgroup -g 1001 -S nodejs && \
+    adduser -S nodejs -u 1001
+
+# Copy package files and install production dependencies only
+COPY package*.json ./
+RUN npm ci --omit=dev && npm cache clean --force
+
+# Copy built frontend assets
+COPY --from=builder /app/dist ./dist
+
+# Copy compiled server JavaScript
+COPY --from=builder /app/server-dist ./server-dist
+
+# Create workspace directory for memory files
+RUN mkdir -p /app/workspace && chown -R nodejs:nodejs /app
+
+# Switch to non-root user
+USER nodejs
+
+# Expose the default port
+EXPOSE 8901
+
+# Set production environment
+ENV NODE_ENV=production
+ENV PORT=8901
+ENV WORKSPACE_DIR=/app/workspace
+ENV STATIC_DIR=/app/dist
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
+  CMD node -e "require('http').get('http://localhost:8901/api/info', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" || exit 1
+
+# Start the server using compiled JavaScript
+CMD ["node", "server-dist/index.js"]
diff --git a/memory-viewer/LICENSE b/memory-viewer/LICENSE
new file mode 100644
index 0000000..85bf460
--- /dev/null
+++ b/memory-viewer/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2026 Silicon Dawn
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/memory-viewer/README.md b/memory-viewer/README.md
new file mode 100644
index 0000000..6ef4904
--- /dev/null
+++ b/memory-viewer/README.md
@@ -0,0 +1,156 @@
+# 📝 Memory Viewer for OpenClaw
+
+[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
+[![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org/)
+[![Version](https://img.shields.io/badge/version-1.2.0-orange.svg)](https://github.com/silicondawn/memory-viewer/releases/tag/v1.2.0)
+[![OpenClaw](https://img.shields.io/badge/OpenClaw-Compatible-blue)](https://openclaw.com)
+
+A beautiful, dark-themed web UI for browsing and editing OpenClaw AI agent memory files. Built specifically for [OpenClaw](https://openclaw.com) agents that store context in Markdown files.
+
+

+ Dashboard (Dark) + Dashboard (Light) +

+

+ Viewer (Dark) + Viewer (Light) +

+

+ Editor + Search +

+ +## Why Memory Viewer for OpenClaw? + +OpenClaw agents store their memory in Markdown files (`MEMORY.md`, `memory/*.md`). Memory Viewer provides a dedicated web interface to: + +- **Browse** memory files in a collapsible tree +- **Search** across all agent memories instantly +- **Edit** files directly in the browser +- **Monitor** agent system status and memory usage +- **Connect** to multiple OpenClaw agents from a single UI + +## Features + +- **📁 File Tree Sidebar** — Navigate all `.md` files in a collapsible tree +- **📖 Markdown Rendering** — GitHub-flavored Markdown with syntax highlighting, tables, and more +- **✏️ In-Browser Editing** — Edit files directly with Ctrl+S to save, with optimistic locking for conflict detection +- **🔍 Full-Text Search** — Search across all memory files instantly (Ctrl+K) +- **📊 System Dashboard** — Server uptime, memory usage, load averages, and today's memory summary +- **🔄 Live Reload** — Files auto-refresh when changed on disk (via WebSocket), with 10s polling fallback +- **📱 PWA Support** — Installable as a standalone app with offline caching +- **🔗 Deep Linking** — Hash-based routing (`#/file/path`) for bookmarkable file URLs +- **📊 Mermaid Diagrams** — Render flowcharts and diagrams from fenced code blocks +- **🚗 Large Screen Optimized** — Touch-friendly UI for car displays (Tesla) and large screens +- **🌙 Dark/Light Theme** — Toggle between themes, designed for always-on dashboards +- **📱 Responsive** — Works on mobile with a slide-out sidebar +- **🌐 Multi-bot Connections** — Connect to multiple OpenClaw agent workspaces from a single UI + +## Quick Start + +```bash +# Clone +git clone https://github.com/silicondawn/memory-viewer.git +cd memory-viewer + +# Install +npm install + +# Run (starts both API server and Vite dev server) +npm run dev +``` + +Then open http://localhost:5173 in your browser. + +## OpenClaw Integration + +Memory Viewer works seamlessly with OpenClaw agents. To connect to your OpenClaw agent: + +1. Make sure your OpenClaw agent is running and accessible +2. In Memory Viewer, click the network icon in the top-right +3. Add your agent's workspace path (e.g., `/home/user/clawd`) +4. Start browsing and editing your agent's memory files + +## Deployment + +Memory Viewer can be deployed as a standalone service: + +```bash +# Build for production +npm run build + +# Start production server +npm start +``` + +The server runs on port 8901 by default. You can expose it via Cloudflare Tunnel, Nginx, or any reverse proxy. + +## Docker Deployment + +### Quick Start (Pre-built Image) + +Use the pre-built image from GitHub Container Registry: + +```bash +# Run directly with docker +docker run -d \ + -p 8901:8901 \ + -v ~/.openclaw/workspace:/app/workspace:ro \ + --name memory-viewer \ + ghcr.io/silicondawn/memory-viewer:latest + +# Or use docker-compose +docker-compose up -d +``` + +Open [http://localhost:8901](http://localhost:8901) in your browser. + +### Build from Source + +```bash +# Clone the repository +git clone https://github.com/silicondawn/memory-viewer.git +cd memory-viewer + +# Build and run +docker-compose up -d --build +``` + +### Docker Configuration + +| Variable | Default | Description | +|----------|---------|-------------| +| `PORT` | `8901` | Container port (fixed in image) | +| `WORKSPACE_DIR` | `/app/workspace` | Directory inside container for `.md` files | +| `STATIC_DIR` | `/app/dist` | Built frontend assets | + +### Customizing the Mount Path + +Edit `docker-compose.yml` to point to your actual OpenClaw workspace: + +```yaml +volumes: + - ~/.openclaw/workspace:/app/workspace:ro + # Windows: C:/Users/YourName/.openclaw/workspace:/app/workspace:ro +``` + +The `:ro` flag mounts the directory as read-only (recommended for safety). + +### Building Custom Images + +Use the provided build script: + +```bash +# Build with default tag (latest) +./scripts/build-docker.sh + +# Build with specific tag +./scripts/build-docker.sh v1.2.0 + +# Build and push to registry +PUSH=true ./scripts/build-docker.sh v1.2.0 +``` + +## License + +MIT © Silicon Dawn \ No newline at end of file diff --git a/memory-viewer/README.zh-CN.md b/memory-viewer/README.zh-CN.md new file mode 100644 index 0000000..8a2f9e2 --- /dev/null +++ b/memory-viewer/README.zh-CN.md @@ -0,0 +1,92 @@ +**中文** | [English](./README.md) + +# 📝 Memory Viewer for OpenClaw + +[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) +[![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org/) +[![Version](https://img.shields.io/badge/version-1.2.0-orange.svg)](https://github.com/silicondawn/memory-viewer/releases/tag/v1.2.0) +[![OpenClaw](https://img.shields.io/badge/OpenClaw-兼容-blue)](https://openclaw.com) + +一个精美的暗色主题 Web UI,用于浏览和编辑 OpenClaw AI Agent 的记忆文件。专为使用 Markdown 文件存储上下文的 [OpenClaw](https://openclaw.com) Agent 设计。 + +

+ 仪表盘(暗色) + 仪表盘(亮色) +

+

+ 阅读模式(暗色) + 阅读模式(亮色) +

+

+ 编辑模式 + 搜索面板 +

+ +## 为什么选择 Memory Viewer for OpenClaw? + +OpenClaw Agent 将记忆存储在 Markdown 文件中(`MEMORY.md`、`memory/*.md`)。Memory Viewer 提供了一个专用的 Web 界面来: + +- **浏览** 记忆文件的折叠树形结构 +- **搜索** 所有 Agent 记忆,即时查找 +- **编辑** 直接在浏览器中修改文件 +- **监控** Agent 系统状态和内存使用情况 +- **连接** 从单个 UI 连接多个 OpenClaw Agent + +## 功能特性 + +- **📁 文件树侧栏** — 可折叠的树形结构,浏览所有 `.md` 文件 +- **📖 Markdown 渲染** — 支持 GFM(GitHub 风格 Markdown),包括语法高亮、表格等 +- **✏️ 浏览器内编辑** — 直接在页面编辑文件,Ctrl+S 保存,支持乐观锁冲突检测 +- **🔍 全文搜索** — 即时搜索所有记忆文件(Ctrl+K) +- **📊 系统仪表盘** — 服务器运行时间、内存使用、负载均值、今日记忆摘要 +- **🔄 实时刷新** — 文件在磁盘上变更时自动刷新(WebSocket + 10 秒轮询兜底) +- **📱 PWA 支持** — 可安装为独立应用,支持离线缓存 +- **🔗 深度链接** — 基于 Hash 路由(`#/file/路径`),可收藏和分享文件 URL +- **📊 Mermaid 图表** — 在围栏代码块中渲染流程图和各类图表 +- **🚗 大屏优化** — 触控友好的 UI,适配车载屏幕(Tesla)和大型显示器 +- **🌙 暗色/亮色主题** — 一键切换,适合常驻仪表盘 +- **📱 响应式设计** — 移动端支持侧滑菜单 +- **🌐 多 Bot 连接** — 在单个 UI 中连接多个 OpenClaw Agent 工作区 + +## 快速开始 + +```bash +# 克隆仓库 +git clone https://github.com/silicondawn/memory-viewer.git +cd memory-viewer + +# 安装依赖 +npm install + +# 启动开发服务器(同时启动 API 和 Vite) +npm run dev +``` + +然后在浏览器中打开 http://localhost:5173。 + +## OpenClaw 集成 + +Memory Viewer 与 OpenClaw Agent 无缝集成。连接到你的 OpenClaw Agent: + +1. 确保你的 OpenClaw Agent 正在运行且可访问 +2. 在 Memory Viewer 中,点击右上角的网络图标 +3. 添加你的 Agent 工作区路径(例如 `/home/user/clawd`) +4. 开始浏览和编辑你的 Agent 记忆文件 + +## 部署 + +Memory Viewer 可以作为独立服务部署: + +```bash +# 构建生产版本 +npm run build + +# 启动生产服务器 +npm start +``` + +服务器默认运行在 8901 端口。你可以通过 Cloudflare Tunnel、Nginx 或任何反向代理暴露它。 + +## 许可证 + +MIT © Silicon Dawn \ No newline at end of file diff --git a/memory-viewer/docker-compose.yml b/memory-viewer/docker-compose.yml new file mode 100644 index 0000000..5062679 --- /dev/null +++ b/memory-viewer/docker-compose.yml @@ -0,0 +1,34 @@ +services: + memory-viewer: + build: + context: . + dockerfile: Dockerfile + container_name: memory-viewer + ports: + - "8901:8901" + environment: + - NODE_ENV=production + - PORT=8901 + - WORKSPACE_DIR=/app/workspace + - STATIC_DIR=/app/dist + volumes: + # Mount your OpenClaw workspace directory here + # Update the path below to match your actual workspace location + - ~/.openclaw/workspace:/app/workspace:ro + # Windows example: + # - C:/Users/YourName/.openclaw/workspace:/app/workspace:ro + # Other Linux/macOS paths: + # - /path/to/your/workspace:/app/workspace:ro + restart: unless-stopped + healthcheck: + test: ["CMD", "node", "-e", "require('http').get('http://localhost:8901/api/info', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s + networks: + - memory-viewer-network + +networks: + memory-viewer-network: + driver: bridge diff --git a/memory-viewer/docs/screenshot-dashboard-dark.png b/memory-viewer/docs/screenshot-dashboard-dark.png new file mode 100644 index 0000000..22dc986 Binary files /dev/null and b/memory-viewer/docs/screenshot-dashboard-dark.png differ diff --git a/memory-viewer/docs/screenshot-dashboard-light.png b/memory-viewer/docs/screenshot-dashboard-light.png new file mode 100644 index 0000000..c1ba8dc Binary files /dev/null and b/memory-viewer/docs/screenshot-dashboard-light.png differ diff --git a/memory-viewer/docs/screenshot-editor-dark.png b/memory-viewer/docs/screenshot-editor-dark.png new file mode 100644 index 0000000..a52381b Binary files /dev/null and b/memory-viewer/docs/screenshot-editor-dark.png differ diff --git a/memory-viewer/docs/screenshot-search-dark.png b/memory-viewer/docs/screenshot-search-dark.png new file mode 100644 index 0000000..32ea4af Binary files /dev/null and b/memory-viewer/docs/screenshot-search-dark.png differ diff --git a/memory-viewer/docs/screenshot-viewer-dark.png b/memory-viewer/docs/screenshot-viewer-dark.png new file mode 100644 index 0000000..d8713fa Binary files /dev/null and b/memory-viewer/docs/screenshot-viewer-dark.png differ diff --git a/memory-viewer/docs/screenshot-viewer-light.png b/memory-viewer/docs/screenshot-viewer-light.png new file mode 100644 index 0000000..5ab2453 Binary files /dev/null and b/memory-viewer/docs/screenshot-viewer-light.png differ diff --git a/memory-viewer/index.html b/memory-viewer/index.html new file mode 100644 index 0000000..c506d9a --- /dev/null +++ b/memory-viewer/index.html @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + Memory Viewer + + +
+ + + + diff --git a/memory-viewer/package-lock.json b/memory-viewer/package-lock.json new file mode 100644 index 0000000..10ccecc --- /dev/null +++ b/memory-viewer/package-lock.json @@ -0,0 +1,8390 @@ +{ + "name": "@silicondawn/memory-viewer", + "version": "1.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@silicondawn/memory-viewer", + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "@codemirror/commands": "^6.10.1", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/language": "^6.12.1", + "@codemirror/language-data": "^6.5.2", + "@codemirror/state": "^6.5.4", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.39.12", + "@hono/node-server": "^1.19.9", + "@hono/node-ws": "^1.3.0", + "@phosphor-icons/react": "^2.1.10", + "@types/better-sqlite3": "^7.6.13", + "beautiful-mermaid": "^0.1.3", + "better-sqlite3": "^12.6.2", + "chokidar": "^4.0.3", + "gray-matter": "^4.0.3", + "hono": "^4.11.7", + "mermaid": "^11.12.2", + "playwright": "^1.58.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-markdown": "^10.1.0", + "rehype-raw": "^7.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "shiki": "^3.22.0", + "ws": "^8.18.2" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.18", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.10.9", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@types/ws": "^8.18.1", + "@vitejs/plugin-react": "^5.1.1", + "concurrently": "^9.2.1", + "jsdom": "^28.0.0", + "tailwindcss": "^4.1.18", + "tsx": "^4.21.0", + "typescript": "~5.9.3", + "vite": "^7.2.4", + "vitest": "^4.0.18" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@acemir/cssom": { + "version": "0.9.31", + "resolved": "https://registry.npmjs.org/@acemir/cssom/-/cssom-0.9.31.tgz", + "integrity": "sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@adobe/css-tools": { + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", + "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@antfu/install-pkg": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@antfu/install-pkg/-/install-pkg-1.1.0.tgz", + "integrity": "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==", + "license": "MIT", + "dependencies": { + "package-manager-detector": "^1.3.0", + "tinyexec": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.1.tgz", + "integrity": "sha512-B0Hv6G3gWGMn0xKJ0txEi/jM5iFpT3MfDxmhZFb4W047GvytCf1DHQ1D69W3zHI4yWe2aTZAA0JnbMZ7Xc8DuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^2.1.4", + "@csstools/css-color-parser": "^3.1.0", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "lru-cache": "^11.2.4" + } + }, + "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.7.tgz", + "integrity": "sha512-8CO/UQ4tzDd7ula+/CVimJIVWez99UJlbMyIgk8xOnhAVPKLnBZmUFYVgugS441v2ZqUq5EnSh6B0Ua0liSFAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.5" + } + }, + "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "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.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@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-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.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "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.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "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/@braintree/sanitize-url": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", + "integrity": "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA==", + "license": "MIT" + }, + "node_modules/@chevrotain/cst-dts-gen": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-11.0.3.tgz", + "integrity": "sha512-BvIKpRLeS/8UbfxXxgC33xOumsacaeCKAjAeLyOn7Pcp95HiRbrpl14S+9vaZLolnbssPIUuiUd8IvgkRyt6NQ==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/gast": "11.0.3", + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/cst-dts-gen/node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/@chevrotain/gast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-11.0.3.tgz", + "integrity": "sha512-+qNfcoNk70PyS/uxmj3li5NiECO+2YKZZQMbmjTqRI3Qchu8Hig/Q9vgkHpI3alNjr7M+a2St5pw5w5F6NL5/Q==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/types": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/@chevrotain/gast/node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/@chevrotain/regexp-to-ast": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/regexp-to-ast/-/regexp-to-ast-11.0.3.tgz", + "integrity": "sha512-1fMHaBZxLFvWI067AVbGJav1eRY7N8DDvYCTwGBiE/ytKBgP8azTdgyrKyWZ9Mfh09eHWb5PgTSO8wi7U824RA==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/types": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-11.0.3.tgz", + "integrity": "sha512-gsiM3G8b58kZC2HaWR50gu6Y1440cHiJ+i3JUvcp/35JchYejb2+5MVeJK0iKThYpAa/P2PYFV4hoi44HD+aHQ==", + "license": "Apache-2.0" + }, + "node_modules/@chevrotain/utils": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-11.0.3.tgz", + "integrity": "sha512-YslZMgtJUyuMbZ+aKvfF3x1f5liK4mWNxghFRv7jqRR9C3R3fAOGTTKvxXDa2Y1s9zSbcpuO0cAxDYsc9SrXoQ==", + "license": "Apache-2.0" + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.0.tgz", + "integrity": "sha512-bOwvTOIJcG5FVo5gUUupiwYh8MioPLQ4UcqbcRf7UQ98X90tCa9E1kZ3Z7tqwpZxYyOvh1YTYbmZE9RTfTp5hg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.10.1.tgz", + "integrity": "sha512-uWDWFypNdQmz2y1LaNJzK7fL7TYKLeUAU0npEC685OKTF3KcQ2Vu3klIM78D7I6wGhktme0lh3CuQLv0ZCrD9Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.4.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-angular": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-angular/-/lang-angular-0.1.4.tgz", + "integrity": "sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.3" + } + }, + "node_modules/@codemirror/lang-cpp": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-cpp/-/lang-cpp-6.0.3.tgz", + "integrity": "sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/cpp": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-css": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" + } + }, + "node_modules/@codemirror/lang-go": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-go/-/lang-go-6.0.1.tgz", + "integrity": "sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/go": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-html": { + "version": "6.4.11", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.11.tgz", + "integrity": "sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.12" + } + }, + "node_modules/@codemirror/lang-java": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-java/-/lang-java-6.0.2.tgz", + "integrity": "sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/java": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-javascript": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.6.0", + "@codemirror/lint": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0", + "@lezer/javascript": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-jinja": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-jinja/-/lang-jinja-6.0.0.tgz", + "integrity": "sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@codemirror/lang-json": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-json/-/lang-json-6.0.2.tgz", + "integrity": "sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/json": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-less": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-less/-/lang-less-6.0.2.tgz", + "integrity": "sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-liquid": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-liquid/-/lang-liquid-6.3.1.tgz", + "integrity": "sha512-S/jE/D7iij2Pu70AC65ME6AYWxOOcX20cSJvaPgY5w7m2sfxsArAcUAuUgm/CZCVmqoi9KiOlS7gj/gyLipABw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-markdown": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-markdown/-/lang-markdown-6.5.0.tgz", + "integrity": "sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.7.1", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.3.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/markdown": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-php": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-php/-/lang-php-6.0.2.tgz", + "integrity": "sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/php": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-python": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-python/-/lang-python-6.2.1.tgz", + "integrity": "sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.3.2", + "@codemirror/language": "^6.8.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.1", + "@lezer/python": "^1.1.4" + } + }, + "node_modules/@codemirror/lang-rust": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-rust/-/lang-rust-6.0.2.tgz", + "integrity": "sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/rust": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sass": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sass/-/lang-sass-6.0.2.tgz", + "integrity": "sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-css": "^6.2.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.0.2", + "@lezer/sass": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-sql": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", + "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-vue": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/lang-vue/-/lang-vue-0.1.3.tgz", + "integrity": "sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-javascript": "^6.1.2", + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.1" + } + }, + "node_modules/@codemirror/lang-wast": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-wast/-/lang-wast-6.0.2.tgz", + "integrity": "sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-xml": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-xml/-/lang-xml-6.1.0.tgz", + "integrity": "sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.4.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/xml": "^1.0.0" + } + }, + "node_modules/@codemirror/lang-yaml": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/@codemirror/lang-yaml/-/lang-yaml-6.1.2.tgz", + "integrity": "sha512-dxrfG8w5Ce/QbT7YID7mWZFKhdhsaTNOYjOkSIMt1qmC4VQnXSDSYVHHHn8k6kJUfIhtLo8t1JJgltlxWdsITw==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.2.0", + "@lezer/lr": "^1.0.0", + "@lezer/yaml": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.1", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.1.tgz", + "integrity": "sha512-Fa6xkSiuGKc8XC8Cn96T+TQHYj4ZZ7RdFmXA3i9xe/3hLHfwPZdM+dqfX0Cp0zQklBKhVD8Yzc8LS45rkqcwpQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/language-data": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/language-data/-/language-data-6.5.2.tgz", + "integrity": "sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==", + "license": "MIT", + "dependencies": { + "@codemirror/lang-angular": "^0.1.0", + "@codemirror/lang-cpp": "^6.0.0", + "@codemirror/lang-css": "^6.0.0", + "@codemirror/lang-go": "^6.0.0", + "@codemirror/lang-html": "^6.0.0", + "@codemirror/lang-java": "^6.0.0", + "@codemirror/lang-javascript": "^6.0.0", + "@codemirror/lang-jinja": "^6.0.0", + "@codemirror/lang-json": "^6.0.0", + "@codemirror/lang-less": "^6.0.0", + "@codemirror/lang-liquid": "^6.0.0", + "@codemirror/lang-markdown": "^6.0.0", + "@codemirror/lang-php": "^6.0.0", + "@codemirror/lang-python": "^6.0.0", + "@codemirror/lang-rust": "^6.0.0", + "@codemirror/lang-sass": "^6.0.0", + "@codemirror/lang-sql": "^6.0.0", + "@codemirror/lang-vue": "^0.1.1", + "@codemirror/lang-wast": "^6.0.0", + "@codemirror/lang-xml": "^6.0.0", + "@codemirror/lang-yaml": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/legacy-modes": "^6.4.0" + } + }, + "node_modules/@codemirror/legacy-modes": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/@codemirror/legacy-modes/-/legacy-modes-6.5.2.tgz", + "integrity": "sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0" + } + }, + "node_modules/@codemirror/lint": { + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.3.tgz", + "integrity": "sha512-y3YkYhdnhjDBAe0VIA0c4wVoFOvnp8CnAvfLqi0TqotIv92wIlAAP7HELOpLBsKwjAX6W92rSflA6an/2zBvXw==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.35.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.5.4.tgz", + "integrity": "sha512-8y7xqG/hpB53l25CIoit9/ngxdfoG+fx+V3SHBrinnhOtLvKHRyAJJuHzkWrR4YXXLX8eXBsejgAAxHUOdW1yw==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.39.12", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.12.tgz", + "integrity": "sha512-f+/VsHVn/kOA9lltk/GFzuYwVVAKmOnNjxbrhkk3tPHntFqjWeI2TbIXx006YkBkqC10wZ4NsnWXCQiFPeAISQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.5.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@dagrejs/dagre": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@dagrejs/dagre/-/dagre-1.1.8.tgz", + "integrity": "sha512-5SEDlndt4W/LaVzPYJW+bSmSEZc9EzTf8rJ20WCKvjS5EAZAN0b+x0Yww7VMT4R3Wootkg+X9bUfUxazYw6Blw==", + "license": "MIT", + "dependencies": { + "@dagrejs/graphlib": "2.2.4" + } + }, + "node_modules/@dagrejs/graphlib": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@dagrejs/graphlib/-/graphlib-2.2.4.tgz", + "integrity": "sha512-mepCf/e9+SKYy1d02/UkvSy6+6MoyXhVxP8lLDfA7BPE1X1d4dR0sZznmbM8/XVJ1GPM+Svnx7Xj6ZweByWUkw==", + "license": "MIT", + "engines": { + "node": ">17.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.11.0.tgz", + "integrity": "sha512-wO3vd8nsEHdumsXrjGO/v4p6irbg7hy9kvIeR6i2AwylZSk4HJdWgL0FNaVquW1+AweJcdvU1IEpuIWk/WaPnA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@hono/node-ws": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@hono/node-ws/-/node-ws-1.3.0.tgz", + "integrity": "sha512-ju25YbbvLuXdqBCmLZLqnNYu1nbHIQjoyUqA8ApZOeL1k4skuiTcw5SW77/5SUYo2Xi2NVBJoVlfQurnKEp03Q==", + "license": "MIT", + "dependencies": { + "ws": "^8.17.0" + }, + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "@hono/node-server": "^1.19.2", + "hono": "^4.6.0" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "license": "MIT" + }, + "node_modules/@iconify/utils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@iconify/utils/-/utils-3.1.0.tgz", + "integrity": "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw==", + "license": "MIT", + "dependencies": { + "@antfu/install-pkg": "^1.1.0", + "@iconify/types": "^2.0.0", + "mlly": "^1.8.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/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/@lezer/common": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.0.tgz", + "integrity": "sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==", + "license": "MIT" + }, + "node_modules/@lezer/cpp": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@lezer/cpp/-/cpp-1.1.5.tgz", + "integrity": "sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/css": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.3.0.tgz", + "integrity": "sha512-pBL7hup88KbI7hXnZV3PQsn43DHy6TWyzuyk2AO9UyoXcDltvIdqWKE1dLL/45JVZ+YZkHe1WVHqO6wugZZWcw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/go": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@lezer/go/-/go-1.0.1.tgz", + "integrity": "sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/html": { + "version": "1.3.13", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.13.tgz", + "integrity": "sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/java": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@lezer/java/-/java-1.1.3.tgz", + "integrity": "sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/javascript": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.4.tgz", + "integrity": "sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" + } + }, + "node_modules/@lezer/json": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@lezer/json/-/json-1.0.3.tgz", + "integrity": "sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.8.tgz", + "integrity": "sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/markdown": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.6.3.tgz", + "integrity": "sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@lezer/php": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@lezer/php/-/php-1.0.5.tgz", + "integrity": "sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.1.0" + } + }, + "node_modules/@lezer/python": { + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/@lezer/python/-/python-1.1.18.tgz", + "integrity": "sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/rust": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@lezer/rust/-/rust-1.0.2.tgz", + "integrity": "sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/sass": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@lezer/sass/-/sass-1.1.0.tgz", + "integrity": "sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/xml": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@lezer/xml/-/xml-1.0.6.tgz", + "integrity": "sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@lezer/yaml": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@lezer/yaml/-/yaml-1.0.4.tgz", + "integrity": "sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.4.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", + "integrity": "sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==", + "license": "MIT" + }, + "node_modules/@mermaid-js/parser": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.6.3.tgz", + "integrity": "sha512-lnjOhe7zyHjc+If7yT4zoedx2vo4sHaTmtkl1+or8BRTnCtDmcTpAjpzDSfCZrshM5bCoz0GyidzadJAH1xobA==", + "license": "MIT", + "dependencies": { + "langium": "3.3.1" + } + }, + "node_modules/@phosphor-icons/react": { + "version": "2.1.10", + "resolved": "https://registry.npmjs.org/@phosphor-icons/react/-/react-2.1.10.tgz", + "integrity": "sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">= 16.8", + "react-dom": ">= 16.8" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.0.tgz", + "integrity": "sha512-tPgXB6cDTndIe1ah7u6amCI1T0SsnlOuKgg10Xh3uizJk4e5M1JGaUMk7J4ciuAUcFpbOiNhm2XIjP9ON0dUqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.0.tgz", + "integrity": "sha512-sa4LyseLLXr1onr97StkU1Nb7fWcg6niokTwEVNOO7awaKaoRObQ54+V/hrF/BP1noMEaaAW6Fg2d/CfLiq3Mg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.0.tgz", + "integrity": "sha512-/NNIj9A7yLjKdmkx5dC2XQ9DmjIECpGpwHoGmA5E1AhU0fuICSqSWScPhN1yLCkEdkCwJIDu2xIeLPs60MNIVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.0.tgz", + "integrity": "sha512-xoh8abqgPrPYPr7pTYipqnUi1V3em56JzE/HgDgitTqZBZ3yKCWI+7KUkceM6tNweyUKYru1UMi7FC060RyKwA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.0.tgz", + "integrity": "sha512-PCkMh7fNahWSbA0OTUQ2OpYHpjZZr0hPr8lId8twD7a7SeWrvT3xJVyza+dQwXSSq4yEQTMoXgNOfMCsn8584g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.0.tgz", + "integrity": "sha512-1j3stGx+qbhXql4OCDZhnK7b01s6rBKNybfsX+TNrEe9JNq4DLi1yGiR1xW+nL+FNVvI4D02PUnl6gJ/2y6WJA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.0.tgz", + "integrity": "sha512-eyrr5W08Ms9uM0mLcKfM/Uzx7hjhz2bcjv8P2uynfj0yU8GGPdz8iYrBPhiLOZqahoAMB8ZiolRZPbbU2MAi6Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.0.tgz", + "integrity": "sha512-Xds90ITXJCNyX9pDhqf85MKWUI4lqjiPAipJ8OLp8xqI2Ehk+TCVhF9rvOoN8xTbcafow3QOThkNnrM33uCFQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.0.tgz", + "integrity": "sha512-Xws2KA4CLvZmXjy46SQaXSejuKPhwVdaNinldoYfqruZBaJHqVo6hnRa8SDo9z7PBW5x84SH64+izmldCgbezw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.0.tgz", + "integrity": "sha512-hrKXKbX5FdaRJj7lTMusmvKbhMJSGWJ+w++4KmjiDhpTgNlhYobMvKfDoIWecy4O60K6yA4SnztGuNTQF+Lplw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.0.tgz", + "integrity": "sha512-6A+nccfSDGKsPm00d3xKcrsBcbqzCTAukjwWK6rbuAnB2bHaL3r9720HBVZ/no7+FhZLz/U3GwwZZEh6tOSI8Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.0.tgz", + "integrity": "sha512-4P1VyYUe6XAJtQH1Hh99THxr0GKMMwIXsRNOceLrJnaHTDgk1FTcTimDgneRJPvB3LqDQxUmroBclQ1S0cIJwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.0.tgz", + "integrity": "sha512-8Vv6pLuIZCMcgXre6c3nOPhE0gjz1+nZP6T+hwWjr7sVH8k0jRkH+XnfjjOTglyMBdSKBPPz54/y1gToSKwrSQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.0.tgz", + "integrity": "sha512-r1te1M0Sm2TBVD/RxBPC6RZVwNqUTwJTA7w+C/IW5v9Ssu6xmxWEi+iJQlpBhtUiT1raJ5b48pI8tBvEjEFnFA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.0.tgz", + "integrity": "sha512-say0uMU/RaPm3CDQLxUUTF2oNWL8ysvHkAjcCzV2znxBr23kFfaxocS9qJm+NdkRhF8wtdEEAJuYcLPhSPbjuQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.0.tgz", + "integrity": "sha512-/MU7/HizQGsnBREtRpcSbSV1zfkoxSTR7wLsRmBPQ8FwUj5sykrP1MyJTvsxP5KBq9SyE6kH8UQQQwa0ASeoQQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.0.tgz", + "integrity": "sha512-Q9eh+gUGILIHEaJf66aF6a414jQbDnn29zeu0eX3dHMuysnhTvsUvZTCAyZ6tJhUjnvzBKE4FtuaYxutxRZpOg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.0.tgz", + "integrity": "sha512-OR5p5yG5OKSxHReWmwvM0P+VTPMwoBS45PXTMYaskKQqybkS3Kmugq1W+YbNWArF8/s7jQScgzXUhArzEQ7x0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.0.tgz", + "integrity": "sha512-XeatKzo4lHDsVEbm1XDHZlhYZZSQYym6dg2X/Ko0kSFgio+KXLsxwJQprnR48GvdIKDOpqWqssC3iBCjoMcMpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.0.tgz", + "integrity": "sha512-Lu71y78F5qOfYmubYLHPcJm74GZLU6UJ4THkf/a1K7Tz2ycwC2VUbsqbJAXaR6Bx70SRdlVrt2+n5l7F0agTUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.0.tgz", + "integrity": "sha512-v5xwKDWcu7qhAEcsUubiav7r+48Uk/ENWdr82MBZZRIm7zThSxCIVDfb3ZeRRq9yqk+oIzMdDo6fCcA5DHfMyA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.0.tgz", + "integrity": "sha512-XnaaaSMGSI6Wk8F4KK3QP7GfuuhjGchElsVerCplUuxRIzdvZ7hRBpLR0omCmw+kI2RFJB80nenhOoGXlJ5TfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.0.tgz", + "integrity": "sha512-3K1lP+3BXY4t4VihLw5MEg6IZD3ojSYzqzBG571W3kNQe4G4CcFpSUQVgurYgib5d+YaCjeFow8QivWp8vuSvA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.0.tgz", + "integrity": "sha512-MDk610P/vJGc5L5ImE4k5s+GZT3en0KoK1MKPXCRgzmksAMk79j4h3k1IerxTNqwDLxsGxStEZVBqG0gIqZqoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.0.tgz", + "integrity": "sha512-Zv7v6q6aV+VslnpwzqKAmrk5JdVkLUzok2208ZXGipjb+msxBr/fJPZyeEXiFgH7k62Ak0SLIfxQRZQvTuf7rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.22.0.tgz", + "integrity": "sha512-iAlTtSDDbJiRpvgL5ugKEATDtHdUVkqgHDm/gbD2ZS9c88mx7G1zSYjjOxp5Qa0eaW0MAQosFRmJSk354PRoQA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.22.0.tgz", + "integrity": "sha512-jdKhfgW9CRtj3Tor0L7+yPwdG3CgP7W+ZEqSsojrMzCjD1e0IxIbwUMDDpYlVBlC08TACg4puwFGkZfLS+56Tw==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.22.0.tgz", + "integrity": "sha512-DyXsOG0vGtNtl7ygvabHd7Mt5EY8gCNqR9Y7Lpbbd/PbJvgWrqaKzH1JW6H6qFkuUa8aCxoiYVv8/YfFljiQxA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.22.0.tgz", + "integrity": "sha512-x/42TfhWmp6H00T6uwVrdTJGKgNdFbrEdhaDwSR5fd5zhQ1Q46bHq9EO61SCEWJR0HY7z2HNDMaBZp8JRmKiIA==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.22.0.tgz", + "integrity": "sha512-o+tlOKqsr6FE4+mYJG08tfCFDS+3CG20HbldXeVoyP+cYSUxDhrFf3GPjE60U55iOkkjbpY2uC3It/eeja35/g==", + "license": "MIT", + "dependencies": { + "@shikijs/types": "3.22.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.22.0.tgz", + "integrity": "sha512-491iAekgKDBFE67z70Ok5a8KBMsQ2IJwOWw3us/7ffQkIBCyOQfm/aNwVMBUriP02QshIfgHCBSIYAl3u2eWjg==", + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@testing-library/user-event": { + "version": "14.6.1", + "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", + "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12", + "npm": ">=6" + }, + "peerDependencies": { + "@testing-library/dom": ">=7.21.4" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "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/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/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/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", + "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT", + "optional": true + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@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==", + "license": "ISC" + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", + "integrity": "sha512-8sCWUyckXXYvx4opfzVY03EOiYVxyNrHS5QxX3DAIi5dpJAAkyJezHCP77VMX4HKA2LDT/Jpfo8i2r5BE3GnQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "chai": "^6.2.1", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.0.18.tgz", + "integrity": "sha512-HhVd0MDnzzsgevnOWCBj5Otnzobjy5wLBe4EdeeFGv8luMsGcYqDuFRMcttKWZA5vVO8RFjexVovXvAM4JoJDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.0.18", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.0.18.tgz", + "integrity": "sha512-P24GK3GulZWC5tz87ux0m8OADrQIUVDPIjjj65vBXYG17ZeU3qD7r+MNZ1RNv4l8CGU2vtTRqixrOi9fYk/yKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.0.18.tgz", + "integrity": "sha512-rpk9y12PGa22Jg6g5M3UVVnTS7+zycIGk9ZNGN+m6tZHKQb7jrP7/77WfZy13Y/EUDd52NDsLRQhYKtv7XfPQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.0.18", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.0.18.tgz", + "integrity": "sha512-PCiV0rcl7jKQjbgYqjtakly6T1uwv/5BQ9SwBLekVg/EaYeQFPiXcgrC2Y7vDMA8dM1SUEAEV82kgSQIlXNMvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.0.18.tgz", + "integrity": "sha512-cbQt3PTSD7P2OARdVW3qWER5EGq7PHlvE+QfzSC0lbwO+xnt7+XH06ZzFjFRgzUX//JmpxrCu92VdwvEPlWSNw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.0.18.tgz", + "integrity": "sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.0.18", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/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/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "license": "MIT", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/beautiful-mermaid": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/beautiful-mermaid/-/beautiful-mermaid-0.1.3.tgz", + "integrity": "sha512-lVEHCnlVLtVRbO03T+D9kY5BZlkpvFU6F18LEu2N2VLB0eo5evG1FJWg3SvREErKY+zZ7j9f+cNsgtiOhYI2Nw==", + "license": "MIT", + "dependencies": { + "@dagrejs/dagre": "^1.1.8" + } + }, + "node_modules/better-sqlite3": { + "version": "12.6.2", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz", + "integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001766", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001766.tgz", + "integrity": "sha512-4C0lfJ0/YPjJQHagaE9x2Elb69CIqEPZeG0anQt9SIvIoOH4a4uaRl73IavyO+0qZh6MDLH//DrXThEYKHkmYA==", + "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/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "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/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chevrotain": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.0.3.tgz", + "integrity": "sha512-ci2iJH6LeIkvP9eJW6gpueU8cnZhv85ELY8w8WiFtNjMHA5ad6pQLaJo9mEly/9qUyCpvqX8/POVUTf18/HFdw==", + "license": "Apache-2.0", + "dependencies": { + "@chevrotain/cst-dts-gen": "11.0.3", + "@chevrotain/gast": "11.0.3", + "@chevrotain/regexp-to-ast": "11.0.3", + "@chevrotain/types": "11.0.3", + "@chevrotain/utils": "11.0.3", + "lodash-es": "4.17.21" + } + }, + "node_modules/chevrotain-allstar": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/chevrotain-allstar/-/chevrotain-allstar-0.3.1.tgz", + "integrity": "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw==", + "license": "MIT", + "dependencies": { + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "chevrotain": "^11.0.0" + } + }, + "node_modules/chevrotain/node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "license": "MIT" + }, + "node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/concurrently/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "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/cose-base": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-1.0.3.tgz", + "integrity": "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==", + "license": "MIT", + "dependencies": { + "layout-base": "^1.0.0" + } + }, + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/cssstyle/node_modules/lru-cache": { + "version": "11.2.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.5.tgz", + "integrity": "sha512-vFrFJkWtJvJnD5hg+hJvVE8Lh/TcMzKnTgCWmtBipwI5yLX/iX+5UB2tfuyODF5E7k9xEzMdYgGqaSb1c0c5Yw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "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/cytoscape": { + "version": "3.33.1", + "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", + "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/cytoscape-cose-bilkent": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cytoscape-cose-bilkent/-/cytoscape-cose-bilkent-4.1.0.tgz", + "integrity": "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^1.0.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cytoscape-fcose/-/cytoscape-fcose-2.2.0.tgz", + "integrity": "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ==", + "license": "MIT", + "dependencies": { + "cose-base": "^2.2.0" + }, + "peerDependencies": { + "cytoscape": "^3.2.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/cose-base": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cose-base/-/cose-base-2.2.0.tgz", + "integrity": "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g==", + "license": "MIT", + "dependencies": { + "layout-base": "^2.0.0" + } + }, + "node_modules/cytoscape-fcose/node_modules/layout-base": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-2.0.1.tgz", + "integrity": "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg==", + "license": "MIT" + }, + "node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "license": "ISC", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, + "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-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "license": "ISC", + "dependencies": { + "d3-path": "1 - 3" + }, + "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-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "license": "ISC", + "dependencies": { + "delaunator": "5" + }, + "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-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "license": "ISC", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "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-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "license": "ISC", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "license": "ISC", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "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-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "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-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-sankey": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/d3-sankey/-/d3-sankey-0.12.3.tgz", + "integrity": "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-array": "1 - 2", + "d3-shape": "^1.2.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-array": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", + "integrity": "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==", + "license": "BSD-3-Clause", + "dependencies": { + "internmap": "^1.0.0" + } + }, + "node_modules/d3-sankey/node_modules/d3-path": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-1.0.9.tgz", + "integrity": "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg==", + "license": "BSD-3-Clause" + }, + "node_modules/d3-sankey/node_modules/d3-shape": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-1.3.7.tgz", + "integrity": "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw==", + "license": "BSD-3-Clause", + "dependencies": { + "d3-path": "1" + } + }, + "node_modules/d3-sankey/node_modules/internmap": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-1.0.1.tgz", + "integrity": "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw==", + "license": "ISC" + }, + "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-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "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/dagre-d3-es": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/dagre-d3-es/-/dagre-d3-es-7.0.13.tgz", + "integrity": "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q==", + "license": "MIT", + "dependencies": { + "d3": "^7.9.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "license": "ISC", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/dompurify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.1.tgz", + "integrity": "sha512-qkdCKzLNtrgPFP1Vo+98FRzJnBRGe4ffyCea9IwHB1fyxPOeNTHpLKYGd4Uk9xvNoH0ZoOjwZxNptyMwqrId1Q==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.282", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.282.tgz", + "integrity": "sha512-FCPkJtpst28UmFzd903iU7PdeVTfY0KAeJy+Lk0GLZRwgwYHn/irRcaCbQQOmr5Vytc/7rcavsYLvTM8RiHYhQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "license": "MIT", + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fault": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fault/-/fault-2.0.1.tgz", + "integrity": "sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==", + "license": "MIT", + "dependencies": { + "format": "^0.2.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/format": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/format/-/format-0.2.2.tgz", + "integrity": "sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", + "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "license": "MIT", + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/hachure-fill": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/hachure-fill/-/hachure-fill-0.5.2.tgz", + "integrity": "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==", + "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/hast-util-from-parse5": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", + "integrity": "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^9.0.0", + "property-information": "^7.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.1.0.tgz", + "integrity": "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/parse5": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz", + "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==", + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.1.tgz", + "integrity": "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hono": { + "version": "4.11.7", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", + "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==", + "license": "MIT" + }, + "node_modules/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-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsdom": { + "version": "28.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-28.0.0.tgz", + "integrity": "sha512-KDYJgZ6T2TKdU8yBfYueq5EPG/EylMsBvCaenWMJb2OXmjgczzwveRCoJ+Hgj1lXPDyasvrgneSn4GBuR1hYyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@acemir/cssom": "^0.9.31", + "@asamuzakjp/dom-selector": "^6.7.6", + "@exodus/bytes": "^1.11.0", + "cssstyle": "^5.3.7", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "undici": "^7.20.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "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/katex": { + "version": "0.16.28", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.28.tgz", + "integrity": "sha512-YHzO7721WbmAL6Ov1uzN/l5mY5WWWhJBSW+jq4tkfZfsxmo1hu6frS0EOswvjBUnWE6NtjEs48SFn5CQESRLZg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/khroma": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/khroma/-/khroma-2.1.0.tgz", + "integrity": "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw==" + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/langium": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/langium/-/langium-3.3.1.tgz", + "integrity": "sha512-QJv/h939gDpvT+9SiLVlY7tZC3xB2qK57v0J04Sh9wpMb6MP1q8gB21L3WIo8T5P1MSMg3Ep14L7KkDCFG3y4w==", + "license": "MIT", + "dependencies": { + "chevrotain": "~11.0.3", + "chevrotain-allstar": "~0.3.0", + "vscode-languageserver": "~9.0.1", + "vscode-languageserver-textdocument": "~1.0.11", + "vscode-uri": "~3.0.8" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/layout-base": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/layout-base/-/layout-base-1.0.2.tgz", + "integrity": "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg==", + "license": "MIT" + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lodash-es": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.23.tgz", + "integrity": "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==", + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/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/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/marked": { + "version": "16.4.2", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.4.2.tgz", + "integrity": "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-frontmatter": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-frontmatter/-/mdast-util-frontmatter-2.0.1.tgz", + "integrity": "sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "escape-string-regexp": "^5.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/mermaid": { + "version": "11.12.2", + "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-11.12.2.tgz", + "integrity": "sha512-n34QPDPEKmaeCG4WDMGy0OT6PSyxKCfy2pJgShP+Qow2KLrvWjclwbc3yXfSIf4BanqWEhQEpngWwNp/XhZt6w==", + "license": "MIT", + "dependencies": { + "@braintree/sanitize-url": "^7.1.1", + "@iconify/utils": "^3.0.1", + "@mermaid-js/parser": "^0.6.3", + "@types/d3": "^7.4.3", + "cytoscape": "^3.29.3", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.2.0", + "d3": "^7.9.0", + "d3-sankey": "^0.12.3", + "dagre-d3-es": "7.0.13", + "dayjs": "^1.11.18", + "dompurify": "^3.2.5", + "katex": "^0.16.22", + "khroma": "^2.1.0", + "lodash-es": "^4.17.21", + "marked": "^16.2.1", + "roughjs": "^4.6.6", + "stylis": "^4.3.6", + "ts-dedent": "^2.2.0", + "uuid": "^11.1.0" + } + }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-frontmatter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-frontmatter/-/micromark-extension-frontmatter-2.0.0.tgz", + "integrity": "sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==", + "license": "MIT", + "dependencies": { + "fault": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/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/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-abi/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/obug": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", + "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==", + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "license": "MIT", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/package-manager-detector": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/package-manager-detector/-/package-manager-detector-1.6.0.tgz", + "integrity": "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==", + "license": "MIT" + }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, + "node_modules/parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", + "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^6.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/path-data-parser": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/path-data-parser/-/path-data-parser-0.1.0.tgz", + "integrity": "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w==", + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/playwright": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.1.tgz", + "integrity": "sha512-+2uTZHxSCcxjvGc5C891LrS1/NlxglGxzrC4seZiVjcYVQfUa87wBL6rTDqzGjuoWNjnBzRqKmF6zRYGMvQUaQ==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.58.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.58.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.1.tgz", + "integrity": "sha512-bcWzOaTxcW+VOOGBCQgnaKToLJ65d6AqfLVKEWvexyS3AS6rbXl+xdpYRMGSRBClPvyj44njOWoxjNdL/H9UNg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/points-on-curve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/points-on-curve/-/points-on-curve-0.2.0.tgz", + "integrity": "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==", + "license": "MIT" + }, + "node_modules/points-on-path": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/points-on-path/-/points-on-path-0.2.1.tgz", + "integrity": "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g==", + "license": "MIT", + "dependencies": { + "path-data-parser": "0.1.0", + "points-on-curve": "0.2.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/pretty-format/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.4" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "license": "MIT" + }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-frontmatter": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/remark-frontmatter/-/remark-frontmatter-5.0.0.tgz", + "integrity": "sha512-XTFYvNASMe5iPN0719nPrdItC9aU0ssC4v14mH1BCi1u0n1gAocqcujWUrByftZTbLhRtiKRyjYTSIOcr69UVQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-frontmatter": "^2.0.0", + "micromark-extension-frontmatter": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==", + "license": "Unlicense" + }, + "node_modules/rollup": { + "version": "4.57.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.0.tgz", + "integrity": "sha512-e5lPJi/aui4TO1LpAXIRLySmwXSE8k3b9zoGfd42p67wzxog4WHjiZF3M2uheQih4DGyc25QEV4yRBbpueNiUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.0", + "@rollup/rollup-android-arm64": "4.57.0", + "@rollup/rollup-darwin-arm64": "4.57.0", + "@rollup/rollup-darwin-x64": "4.57.0", + "@rollup/rollup-freebsd-arm64": "4.57.0", + "@rollup/rollup-freebsd-x64": "4.57.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.0", + "@rollup/rollup-linux-arm-musleabihf": "4.57.0", + "@rollup/rollup-linux-arm64-gnu": "4.57.0", + "@rollup/rollup-linux-arm64-musl": "4.57.0", + "@rollup/rollup-linux-loong64-gnu": "4.57.0", + "@rollup/rollup-linux-loong64-musl": "4.57.0", + "@rollup/rollup-linux-ppc64-gnu": "4.57.0", + "@rollup/rollup-linux-ppc64-musl": "4.57.0", + "@rollup/rollup-linux-riscv64-gnu": "4.57.0", + "@rollup/rollup-linux-riscv64-musl": "4.57.0", + "@rollup/rollup-linux-s390x-gnu": "4.57.0", + "@rollup/rollup-linux-x64-gnu": "4.57.0", + "@rollup/rollup-linux-x64-musl": "4.57.0", + "@rollup/rollup-openbsd-x64": "4.57.0", + "@rollup/rollup-openharmony-arm64": "4.57.0", + "@rollup/rollup-win32-arm64-msvc": "4.57.0", + "@rollup/rollup-win32-ia32-msvc": "4.57.0", + "@rollup/rollup-win32-x64-gnu": "4.57.0", + "@rollup/rollup-win32-x64-msvc": "4.57.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/roughjs": { + "version": "4.6.6", + "resolved": "https://registry.npmjs.org/roughjs/-/roughjs-4.6.6.tgz", + "integrity": "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ==", + "license": "MIT", + "dependencies": { + "hachure-fill": "^0.5.2", + "path-data-parser": "^0.1.0", + "points-on-curve": "^0.2.0", + "points-on-path": "^0.2.1" + } + }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==", + "license": "BSD-3-Clause" + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "license": "MIT", + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/shiki": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.22.0.tgz", + "integrity": "sha512-LBnhsoYEe0Eou4e1VgJACes+O6S6QC0w71fCSp5Oya79inkwkm15gQ1UF6VtQ8j/taMDh79hAB49WUk8ALQW3g==", + "license": "MIT", + "dependencies": { + "@shikijs/core": "3.22.0", + "@shikijs/engine-javascript": "3.22.0", + "@shikijs/engine-oniguruma": "3.22.0", + "@shikijs/langs": "3.22.0", + "@shikijs/themes": "3.22.0", + "@shikijs/types": "3.22.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "license": "BSD-3-Clause" + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/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-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/style-mod": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.3.tgz", + "integrity": "sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==", + "license": "MIT" + }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, + "node_modules/stylis": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz", + "integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==", + "license": "MIT" + }, + "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/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.2.tgz", + "integrity": "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.0.3.tgz", + "integrity": "sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.21.tgz", + "integrity": "sha512-Plu6V8fF/XU6d2k8jPtlQf5F4Xx2hAin4r2C2ca7wR8NK5MbRTo9huLUWRe28f3Uk8bYZfg74tit/dSjc18xnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.0.21" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.0.21", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.21.tgz", + "integrity": "sha512-oVOMdHvgjqyzUZH1rOESgJP1uNe2bVrfK0jUHHmiM2rpEiRbf3j4BrsIc6JigJRbHGanQwuZv/R+LTcHsw+bLA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.0.tgz", + "integrity": "sha512-kXuRi1mtaKMrsLUxz3sQYvVl37B0Ns6MzfrtV5DvJceE9bPyspOqk9xxv7XbZWcfLWbFmm997vl83qUWVJA64w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-dedent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ts-dedent/-/ts-dedent-2.2.0.tgz", + "integrity": "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==", + "license": "MIT", + "engines": { + "node": ">=6.10" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/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/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "license": "MIT" + }, + "node_modules/undici": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.20.0.tgz", + "integrity": "sha512-MJZrkjyd7DeC+uPZh+5/YaMDxFiiEEaDgbUSVMXayofAkDWF1088CDo+2RPg7B1BuS1qf1vgNE7xqwPxE0DuSQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/uuid": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/esm/bin/uuid" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", + "integrity": "sha512-hOQuK7h0FGKgBAas7v0mSAsnvrIgAvWmRFjmzpJ7SwFHH3g1k2u37JtYwOwmEKhK6ZO3v9ggDBBm0La1LCK4uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.0.18", + "@vitest/mocker": "4.0.18", + "@vitest/pretty-format": "4.0.18", + "@vitest/runner": "4.0.18", + "@vitest/snapshot": "4.0.18", + "@vitest/spy": "4.0.18", + "@vitest/utils": "4.0.18", + "es-module-lexer": "^1.7.0", + "expect-type": "^1.2.2", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^3.10.0", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.0.3", + "vite": "^6.0.0 || ^7.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.0.18", + "@vitest/browser-preview": "4.0.18", + "@vitest/browser-webdriverio": "4.0.18", + "@vitest/ui": "4.0.18", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vscode-jsonrpc": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", + "integrity": "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/vscode-languageserver": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-9.0.1.tgz", + "integrity": "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==", + "license": "MIT", + "dependencies": { + "vscode-languageserver-protocol": "3.17.5" + }, + "bin": { + "installServerIntoExtension": "bin/installServerIntoExtension" + } + }, + "node_modules/vscode-languageserver-protocol": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.5.tgz", + "integrity": "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==", + "license": "MIT", + "dependencies": { + "vscode-jsonrpc": "8.2.0", + "vscode-languageserver-types": "3.17.5" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.12.tgz", + "integrity": "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==", + "license": "MIT" + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.5", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.5.tgz", + "integrity": "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==", + "license": "MIT" + }, + "node_modules/vscode-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.8.tgz", + "integrity": "sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==", + "license": "MIT" + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.0.tgz", + "integrity": "sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", + "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "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/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/memory-viewer/package.json b/memory-viewer/package.json new file mode 100644 index 0000000..a3ca25f --- /dev/null +++ b/memory-viewer/package.json @@ -0,0 +1,83 @@ +{ + "name": "@silicondawn/memory-viewer", + "version": "1.2.0", + "description": "A beautiful web-based viewer for OpenClaw agent memory files. Browse, search, and edit Markdown files with a dark-themed UI.", + "type": "module", + "license": "MIT", + "author": "Silicon Dawn", + "homepage": "https://github.com/silicondawn/memory-viewer", + "repository": { + "type": "git", + "url": "https://github.com/silicondawn/memory-viewer" + }, + "bugs": { + "url": "https://github.com/silicondawn/memory-viewer/issues" + }, + "engines": { + "node": ">=18" + }, + "keywords": [ + "openclaw", + "memory", + "markdown", + "viewer", + "agent" + ], + "scripts": { + "dev": "concurrently -n api,web -c blue,green \"npm run server:dev\" \"vite\"", + "build": "vite build", + "start": "node server/dist/index.js", + "server:dev": "tsx watch server/index.ts", + "server:build": "tsx server/index.ts", + "preview": "npm run build && npm start", + "typecheck": "tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "@codemirror/commands": "^6.10.1", + "@codemirror/lang-markdown": "^6.5.0", + "@codemirror/language": "^6.12.1", + "@codemirror/language-data": "^6.5.2", + "@codemirror/state": "^6.5.4", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.39.12", + "@hono/node-server": "^1.19.9", + "@hono/node-ws": "^1.3.0", + "@phosphor-icons/react": "^2.1.10", + "@types/better-sqlite3": "^7.6.13", + "beautiful-mermaid": "^0.1.3", + "better-sqlite3": "^12.6.2", + "chokidar": "^4.0.3", + "gray-matter": "^4.0.3", + "hono": "^4.11.7", + "mermaid": "^11.12.2", + "playwright": "^1.58.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "react-markdown": "^10.1.0", + "rehype-raw": "^7.0.0", + "remark-frontmatter": "^5.0.0", + "remark-gfm": "^4.0.1", + "shiki": "^3.22.0", + "ws": "^8.18.2" + }, + "devDependencies": { + "@tailwindcss/vite": "^4.1.18", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@testing-library/user-event": "^14.6.1", + "@types/node": "^24.10.9", + "@types/react": "^19.2.5", + "@types/react-dom": "^19.2.3", + "@types/ws": "^8.18.1", + "@vitejs/plugin-react": "^5.1.1", + "concurrently": "^9.2.1", + "jsdom": "^28.0.0", + "tailwindcss": "^4.1.18", + "tsx": "^4.21.0", + "typescript": "~5.9.3", + "vite": "^7.2.4", + "vitest": "^4.0.18" + } +} diff --git a/memory-viewer/public/icon-192.png b/memory-viewer/public/icon-192.png new file mode 100644 index 0000000..439cfdd Binary files /dev/null and b/memory-viewer/public/icon-192.png differ diff --git a/memory-viewer/public/icon-512.png b/memory-viewer/public/icon-512.png new file mode 100644 index 0000000..ea46532 Binary files /dev/null and b/memory-viewer/public/icon-512.png differ diff --git a/memory-viewer/public/manifest.json b/memory-viewer/public/manifest.json new file mode 100644 index 0000000..2fe67e0 --- /dev/null +++ b/memory-viewer/public/manifest.json @@ -0,0 +1,21 @@ +{ + "name": "Memory Viewer", + "short_name": "Memory", + "description": "Browse and edit your AI agent's memory files", + "start_url": "/", + "display": "standalone", + "background_color": "#030712", + "theme_color": "#030712", + "icons": [ + { + "src": "/icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/memory-viewer/public/sw.js b/memory-viewer/public/sw.js new file mode 100644 index 0000000..9c0c26a --- /dev/null +++ b/memory-viewer/public/sw.js @@ -0,0 +1,60 @@ +const CACHE_NAME = 'memory-viewer-v1'; +const STATIC_ASSETS = ['/']; + +// Install: cache shell +self.addEventListener('install', (e) => { + e.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(STATIC_ASSETS)) + ); + self.skipWaiting(); +}); + +// Activate: clean old caches +self.addEventListener('activate', (e) => { + e.waitUntil( + caches.keys().then((keys) => + Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k))) + ) + ); + self.clients.claim(); +}); + +// Fetch: network-first for API, cache-first for assets +self.addEventListener('fetch', (e) => { + const url = new URL(e.request.url); + + // API calls: network only + if (url.pathname.startsWith('/api/') || url.pathname.startsWith('/ws')) { + return; + } + + // Assets with hash in filename: cache-first (immutable) + if (url.pathname.startsWith('/assets/') && url.pathname.match(/\-[a-zA-Z0-9_-]{8}\./)) { + e.respondWith( + caches.match(e.request).then((cached) => { + if (cached) return cached; + return fetch(e.request).then((resp) => { + if (resp.ok) { + const clone = resp.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(e.request, clone)); + } + return resp; + }); + }) + ); + return; + } + + // HTML: network-first, fallback to cache + e.respondWith( + fetch(e.request) + .then((resp) => { + if (resp.ok) { + const clone = resp.clone(); + caches.open(CACHE_NAME).then((cache) => cache.put(e.request, clone)); + } + return resp; + }) + .catch(() => caches.match(e.request)) + ); +}); diff --git a/memory-viewer/scripts/build-docker.sh b/memory-viewer/scripts/build-docker.sh new file mode 100755 index 0000000..c33704c --- /dev/null +++ b/memory-viewer/scripts/build-docker.sh @@ -0,0 +1,59 @@ +#!/bin/bash +set -e + +# Docker Image Build Script for Memory Viewer +# Usage: ./scripts/build-docker.sh [tag] + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +# Default values +REGISTRY="${DOCKER_REGISTRY:-ghcr.io}" +IMAGE_NAME="${DOCKER_IMAGE_NAME:-silicondawn/memory-viewer}" +TAG="${1:-latest}" +FULL_IMAGE="$REGISTRY/$IMAGE_NAME:$TAG" + +echo "🐳 Building Memory Viewer Docker Image" +echo "======================================" +echo "Registry: $REGISTRY" +echo "Image: $IMAGE_NAME" +echo "Tag: $TAG" +echo "Full: $FULL_IMAGE" +echo "" + +# Navigate to project root +cd "$PROJECT_ROOT" + +# Build the image +echo "📦 Building Docker image..." +docker build -t "$FULL_IMAGE" . + +# Tag additional tags +if [ "$TAG" != "latest" ]; then + docker tag "$FULL_IMAGE" "$REGISTRY/$IMAGE_NAME:latest" + echo "🏷️ Tagged as latest" +fi + +echo "" +echo "✅ Build complete!" +echo "" + +# Push if requested +if [ "${PUSH:-false}" = "true" ]; then + echo "📤 Pushing to registry..." + docker push "$FULL_IMAGE" + if [ "$TAG" != "latest" ]; then + docker push "$REGISTRY/$IMAGE_NAME:latest" + fi + echo "✅ Push complete!" +else + echo "💡 To push the image, run: PUSH=true $0 $TAG" + echo " Or manually: docker push $FULL_IMAGE" +fi + +echo "" +echo "🚀 To run the container:" +echo " docker run -d -p 8901:8901 -v ~/.openclaw/workspace:/app/workspace:ro $FULL_IMAGE" +echo "" +echo " Or use docker-compose:" +echo " docker-compose up -d" diff --git a/memory-viewer/server-dist/api.test.js b/memory-viewer/server-dist/api.test.js new file mode 100644 index 0000000..25eadb1 --- /dev/null +++ b/memory-viewer/server-dist/api.test.js @@ -0,0 +1,41 @@ +import { describe, it, expect } from 'vitest'; +import { app } from './index.js'; +describe('Server API', () => { + it('GET /api/info returns bot info', async () => { + // Note: This relies on actual file system unless mocked. + // Ideally we mock fs, but for integration test on CI it's fine. + const res = await app.request('/api/info'); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data).toHaveProperty('version'); + }); + it('GET /api/agent/status returns structure', async () => { + const res = await app.request('/api/agent/status'); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data).toHaveProperty('config'); + expect(data).toHaveProperty('gateway'); + }); +}); +describe('Backlinks API', () => { + // These tests rely on the actual WORKSPACE directory (~/clawd by default) + // They test the API structure and basic behavior + it('GET /api/resolve-wikilink returns 400 without link', async () => { + const res = await app.request('/api/resolve-wikilink'); + expect(res.status).toBe(400); + }); + it('GET /api/resolve-wikilink resolves existing file', async () => { + // MEMORY.md should exist in the workspace + const res = await app.request('/api/resolve-wikilink?link=MEMORY'); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data).toHaveProperty('found'); + expect(data).toHaveProperty('path'); + }); + it('GET /api/resolve-wikilink returns not found for nonexistent', async () => { + const res = await app.request('/api/resolve-wikilink?link=nonexistent-file-that-does-not-exist-12345'); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data.found).toBe(false); + }); +}); diff --git a/memory-viewer/server-dist/index.js b/memory-viewer/server-dist/index.js new file mode 100644 index 0000000..aa44f3e --- /dev/null +++ b/memory-viewer/server-dist/index.js @@ -0,0 +1,1134 @@ +/** + * Memory Viewer — API Server (Hono) + * + * Provides REST endpoints for browsing, reading, editing, and searching + * Markdown files, plus a WebSocket channel that pushes live file-change + * notifications to connected clients. + */ +import { Hono } from "hono"; +import { compress } from "hono/compress"; +import { cors } from "hono/cors"; +import { serveStatic } from "@hono/node-server/serve-static"; +import { createNodeWebSocket } from "@hono/node-ws"; +import { serve } from "@hono/node-server"; +import fs from "fs"; +import path from "path"; +import os from "os"; +import Database from "better-sqlite3"; +import { exec as execCallback } from "child_process"; +import util from "util"; +import { watch } from "chokidar"; +const exec = util.promisify(execCallback); +// --------------------------------------------------------------------------- +// Settings +// --------------------------------------------------------------------------- +const SETTINGS_DIR = path.join(os.homedir(), ".config", "memory-viewer"); +const SETTINGS_FILE = path.join(SETTINGS_DIR, "settings.json"); +const DEFAULT_SETTINGS = { + embedding: { enabled: false, apiUrl: "", apiKey: "", model: "" }, + pluginsDir: "", +}; +function loadSettings() { + try { + const raw = fs.readFileSync(SETTINGS_FILE, "utf-8"); + return { ...DEFAULT_SETTINGS, ...JSON.parse(raw) }; + } + catch { + return { ...DEFAULT_SETTINGS }; + } +} +function saveSettings(s) { + fs.mkdirSync(SETTINGS_DIR, { recursive: true }); + fs.writeFileSync(SETTINGS_FILE, JSON.stringify(s, null, 2)); +} +let appSettings = loadSettings(); +// --------------------------------------------------------------------------- +// Config +// --------------------------------------------------------------------------- +const PORT = Number(process.env.PORT) || 3001; +const DEFAULT_WORKSPACE = process.env.WORKSPACE_DIR || path.join(os.homedir(), "clawd"); +const STATIC_DIR = process.env.STATIC_DIR || path.join(import.meta.dirname, "..", "dist"); +const app = new Hono(); +export { app }; // Export for testing +const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }); +app.use("*", compress()); +app.use("*", cors({ origin: "*" })); +// --------------------------------------------------------------------------- +// Agent Management +// --------------------------------------------------------------------------- +const OPENCLAW_CONFIG_PATH = path.join(os.homedir(), ".openclaw", "openclaw.json"); +function loadOpenClawConfig() { + try { + if (fs.existsSync(OPENCLAW_CONFIG_PATH)) { + const raw = fs.readFileSync(OPENCLAW_CONFIG_PATH, "utf-8"); + return JSON.parse(raw); + } + } + catch (e) { + console.error("Failed to load OpenClaw config:", e); + } + return null; +} +function getAgentWorkspace(agentConfig, defaults) { + // Priority: workspace > agentDir > defaults.workspace > DEFAULT_WORKSPACE + if (agentConfig.workspace) { + return agentConfig.workspace; + } + if (agentConfig.agentDir) { + return agentConfig.agentDir; + } + if (defaults.workspace) { + return defaults.workspace; + } + return DEFAULT_WORKSPACE; +} +function getAgents() { + const config = loadOpenClawConfig(); + if (!config?.agents?.list) { + // Return default agent if no config + return [{ + id: "default", + name: "Default Agent", + workspace: DEFAULT_WORKSPACE, + emoji: "🤖", + }]; + } + const defaults = config.agents.defaults || {}; + return config.agents.list.map((agent) => ({ + id: agent.id, + name: agent.name || agent.id, + workspace: getAgentWorkspace(agent, defaults), + emoji: agent.identity?.emoji || "🤖", + skills: agent.skills || undefined, + })); +} +function getAgentById(agentId) { + const agents = getAgents(); + return agents.find((a) => a.id === agentId) || null; +} +// Get workspace for a given agent ID +function getWorkspaceForAgent(agentId) { + if (!agentId || agentId === "default") { + // Try to find "default" agent in config, otherwise use default workspace + const agent = getAgentById("default"); + if (agent) + return agent.workspace; + return DEFAULT_WORKSPACE; + } + const agent = getAgentById(agentId); + if (agent) + return agent.workspace; + // Fallback to default workspace if agent not found + return DEFAULT_WORKSPACE; +} +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- +function safePath(filePath, workspace) { + if (!filePath || filePath.includes("..") || !filePath.endsWith(".md")) + return null; + const full = path.resolve(workspace, filePath); + if (!full.startsWith(path.resolve(workspace))) + return null; + return full; +} +function scanDir(dir, prefix = "") { + const result = []; + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } + catch { + return result; + } + for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { + if (entry.name.startsWith(".") || entry.name === "node_modules") + continue; + const relPath = prefix ? `${prefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) { + const children = scanDir(path.join(dir, entry.name), relPath); + if (children.length > 0) { + result.push({ name: entry.name, type: "dir", path: relPath, children }); + } + } + else if (entry.name.endsWith(".md")) { + result.push({ name: entry.name, type: "file", path: relPath }); + } + } + return result; +} +function collectMdFiles(dir, prefix = "") { + const files = []; + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } + catch { + return files; + } + for (const entry of entries) { + if (entry.name.startsWith(".") || entry.name === "node_modules") + continue; + const relPath = prefix ? `${prefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) { + files.push(...collectMdFiles(path.join(dir, entry.name), relPath)); + } + else if (entry.name.endsWith(".md")) { + files.push(relPath); + } + } + return files; +} +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// REST API +// --------------------------------------------------------------------------- +// Get agent from query parameter +function getAgentFromQuery(c) { + const agentId = c.req.query("agent") || "default"; + const workspace = getWorkspaceForAgent(agentId); + return { agentId, workspace }; +} +// Agents API +app.get("/api/agents", (c) => { + return c.json(getAgents()); +}); +app.get("/api/skills", (c) => { + const { workspace } = getAgentFromQuery(c); + const skillsDir = path.join(workspace, "skills"); + const results = []; + let entries; + try { + entries = fs.readdirSync(skillsDir, { withFileTypes: true }); + } + catch { + return c.json([]); + } + for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { + if (!entry.isDirectory() || entry.name.startsWith(".")) + continue; + const skillMd = path.join(skillsDir, entry.name, "SKILL.md"); + if (!fs.existsSync(skillMd)) + continue; + const content = fs.readFileSync(skillMd, "utf-8"); + let name = entry.name; + let description = ""; + const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (fmMatch) { + const nameMatch = fmMatch[1].match(/^name:\s*(.+)/m); + const descMatch = fmMatch[1].match(/^description:\s*(.+)/m); + if (nameMatch) + name = nameMatch[1].trim().replace(/^["']|["']$/g, ""); + if (descMatch) + description = descMatch[1].trim().replace(/^["']|["']$/g, ""); + } + results.push({ id: entry.name, name, description, path: `skills/${entry.name}/SKILL.md` }); + } + return c.json(results); +}); +app.get("/api/files", (c) => { + const { workspace } = getAgentFromQuery(c); + return c.json(scanDir(workspace)); +}); +app.get("/api/file", (c) => { + const { workspace } = getAgentFromQuery(c); + const full = safePath(c.req.query("path"), workspace); + if (!full) + return c.json({ error: "Invalid path" }, 400); + if (!fs.existsSync(full)) + return c.json({ error: "Not found" }, 404); + const content = fs.readFileSync(full, "utf-8"); + const stat = fs.statSync(full); + return c.json({ content, mtime: stat.mtime, size: stat.size }); +}); +app.put("/api/file", async (c) => { + const { workspace } = getAgentFromQuery(c); + const { path: filePath, content, expectedMtime } = await c.req.json(); + const full = safePath(filePath, workspace); + if (!full) + return c.json({ error: "Invalid path" }, 400); + if (expectedMtime && fs.existsSync(full)) { + const currentMtime = fs.statSync(full).mtime.toISOString(); + if (currentMtime !== expectedMtime) { + const currentContent = fs.readFileSync(full, "utf-8"); + return c.json({ + error: "conflict", + message: "File was modified since you started editing", + serverMtime: currentMtime, + serverContent: currentContent, + }, 409); + } + } + fs.mkdirSync(path.dirname(full), { recursive: true }); + fs.writeFileSync(full, content, "utf-8"); + const stat = fs.statSync(full); + return c.json({ ok: true, mtime: stat.mtime }); +}); +app.get("/api/resolve-wikilink", (c) => { + const { workspace } = getAgentFromQuery(c); + const link = (c.req.query("link") || "").trim(); + if (!link) + return c.json({ error: "Missing link parameter" }, 400); + const allFiles = collectMdFiles(workspace); + // Try exact path match first + const exactPath = link.endsWith(".md") ? link : `${link}.md`; + if (allFiles.includes(exactPath)) { + return c.json({ found: true, path: exactPath }); + } + // Try case-insensitive exact path + const exactLower = exactPath.toLowerCase(); + const ciMatch = allFiles.find((f) => f.toLowerCase() === exactLower); + if (ciMatch) { + return c.json({ found: true, path: ciMatch }); + } + // Try filename-only match (fuzzy) + const linkLower = link.toLowerCase(); + const byName = allFiles.find((f) => { + const name = path.basename(f, ".md"); + return name.toLowerCase() === linkLower; + }); + if (byName) { + return c.json({ found: true, path: byName }); + } + return c.json({ found: false, path: null }); +}); +app.get("/api/search", (c) => { + const { workspace } = getAgentFromQuery(c); + const q = (c.req.query("q") || "").trim().toLowerCase(); + if (!q || q.length < 2) + return c.json([]); + const files = collectMdFiles(workspace); + const results = []; + for (const relPath of files) { + const full = path.join(workspace, relPath); + let content; + try { + content = fs.readFileSync(full, "utf-8"); + } + catch { + continue; + } + const lines = content.split("\n"); + const matches = []; + for (let i = 0; i < lines.length; i++) { + if (lines[i].toLowerCase().includes(q)) { + matches.push({ line: i + 1, text: lines[i].substring(0, 200) }); + if (matches.length >= 5) + break; + } + } + if (matches.length > 0) + results.push({ path: relPath, matches }); + if (results.length >= 50) + break; + } + return c.json(results); +}); +// QMD availability detection — cached at startup +let qmdAvailable = null; +let qmdHasVectors = false; +async function detectQmd() { + try { + const { stdout } = await exec(`export PATH="$HOME/.bun/bin:$PATH" && qmd status 2>/dev/null`, { timeout: 5000 }); + qmdAvailable = stdout.includes("Documents"); + qmdHasVectors = /Vectors:\s*[1-9]/.test(stdout); + console.log(`🔍 QMD: ${qmdAvailable ? "available" : "not found"}${qmdHasVectors ? " (vectors ready)" : ""}`); + } + catch { + qmdAvailable = false; + qmdHasVectors = false; + console.log("🔍 QMD: not installed"); + } +} +// Run detection on startup +detectQmd(); +// External plugins directory +const PLUGINS_DIR = appSettings.pluginsDir || process.env.PLUGINS_DIR || ""; +app.get("/api/plugins", (c) => { + if (!PLUGINS_DIR || !fs.existsSync(PLUGINS_DIR)) + return c.json([]); + try { + const plugins = []; + for (const dir of fs.readdirSync(PLUGINS_DIR, { withFileTypes: true })) { + if (!dir.isDirectory()) + continue; + const manifestPath = path.join(PLUGINS_DIR, dir.name, "plugin.json"); + if (fs.existsSync(manifestPath)) { + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")); + plugins.push({ id: manifest.id || dir.name, name: manifest.name || dir.name, entry: manifest.entry || "index.js" }); + } + } + return c.json(plugins); + } + catch { + return c.json([]); + } +}); +// Serve plugin files +app.get("/api/plugins/:id/*", (c) => { + if (!PLUGINS_DIR) + return c.text("No plugins dir", 404); + const pluginId = c.req.param("id"); + const filePath = c.req.path.replace(`/api/plugins/${pluginId}/`, ""); + const full = path.join(PLUGINS_DIR, pluginId, filePath); + if (!full.startsWith(path.join(PLUGINS_DIR, pluginId))) + return c.text("Forbidden", 403); + if (!fs.existsSync(full)) + return c.text("Not found", 404); + const content = fs.readFileSync(full, "utf-8"); + const ext = path.extname(full); + const ct = ext === ".js" ? "application/javascript" : ext === ".css" ? "text/css" : "text/plain"; + return c.text(content, 200, { "Content-Type": ct }); +}); +app.get("/api/capabilities", (c) => { + const embeddingReady = appSettings.embedding.enabled && !!appSettings.embedding.apiUrl && !!appSettings.embedding.apiKey; + return c.json({ + qmd: qmdAvailable === true, + qmdBm25: qmdAvailable === true, + qmdVector: qmdHasVectors || embeddingReady, + embeddingApi: embeddingReady, + }); +}); +// Settings API +app.get("/api/settings", (c) => { + return c.json({ + embedding: { + enabled: appSettings.embedding.enabled, + apiUrl: appSettings.embedding.apiUrl, + apiKeySet: !!appSettings.embedding.apiKey, + model: appSettings.embedding.model, + }, + }); +}); +app.put("/api/settings", async (c) => { + const body = await c.req.json(); + if (body.embedding) { + appSettings.embedding.enabled = body.embedding.enabled ?? appSettings.embedding.enabled; + appSettings.embedding.apiUrl = body.embedding.apiUrl ?? appSettings.embedding.apiUrl; + appSettings.embedding.model = body.embedding.model ?? appSettings.embedding.model; + if (body.embedding.apiKey) { + appSettings.embedding.apiKey = body.embedding.apiKey; + } + } + saveSettings(appSettings); + return c.json({ ok: true }); +}); +app.get("/api/settings/embedding-stats", (c) => { + const { workspace } = getAgentFromQuery(c); + try { + const db = getEmbeddingsDb(); + const model = appSettings.embedding.model || "text-embedding-3-small"; + const total = db.prepare("SELECT COUNT(*) as n FROM embeddings WHERE model = ?").get(model)?.n || 0; + const allFiles = collectMdFiles(workspace).length; + const dbSize = fs.existsSync(EMBEDDINGS_DB_PATH) ? fs.statSync(EMBEDDINGS_DB_PATH).size : 0; + return c.json({ + cachedFiles: total, + totalFiles: allFiles, + coverage: allFiles > 0 ? Math.round((total / allFiles) * 100) : 0, + dbSize, + model, + }); + } + catch { + return c.json({ cachedFiles: 0, totalFiles: 0, coverage: 0, dbSize: 0, model: "" }); + } +}); +app.post("/api/settings/test-embedding", async (c) => { + const { apiUrl, apiKey, model } = await c.req.json(); + const url = apiUrl || appSettings.embedding.apiUrl; + const key = apiKey || appSettings.embedding.apiKey; + const mdl = model || appSettings.embedding.model || "text-embedding-3-small"; + if (!url) + return c.json({ ok: false, message: "API URL is required" }); + try { + const resp = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(key ? { Authorization: `Bearer ${key}` } : {}), + }, + body: JSON.stringify({ input: "test", model: mdl }), + signal: AbortSignal.timeout(10000), + }); + const data = await resp.json(); + if (data.data?.[0]?.embedding) { + const dim = data.data[0].embedding.length; + return c.json({ ok: true, message: `✅ ${dim}维向量` }); + } + return c.json({ ok: false, message: data.error?.message || "Unexpected response" }); + } + catch (e) { + return c.json({ ok: false, message: e.message || "Connection failed" }); + } +}); +function qmdUriToRelPath(uri) { + // qmd://clawd-memory/memory/survival.md → memory/survival.md + // qmd://clawd-root/MEMORY.md → MEMORY.md + // All collections index from workspace root, so just strip the qmd://collection/ prefix + const match = uri.match(/^qmd:\/\/[^/]+\/(.+)$/); + return match ? match[1] : uri; +} +// Embedding API helper +async function getEmbedding(text) { + const { apiUrl, apiKey, model } = appSettings.embedding; + if (!apiUrl || !apiKey) + return null; + try { + const resp = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ input: text, model: model || "text-embedding-3-small" }), + signal: AbortSignal.timeout(10000), + }); + const data = await resp.json(); + return data.data?.[0]?.embedding ?? null; + } + catch { + return null; + } +} +function cosineSim(a, b) { + let dot = 0, na = 0, nb = 0; + for (let i = 0; i < a.length; i++) { + dot += a[i] * b[i]; + na += a[i] * a[i]; + nb += b[i] * b[i]; + } + return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-10); +} +// SQLite embedding cache — persistent across restarts +const EMBEDDINGS_DB_PATH = path.join(SETTINGS_DIR, "embeddings.sqlite"); +let embeddingsDb = null; +function getEmbeddingsDb() { + if (!embeddingsDb) { + fs.mkdirSync(SETTINGS_DIR, { recursive: true }); + embeddingsDb = new Database(EMBEDDINGS_DB_PATH); + embeddingsDb.pragma("journal_mode = WAL"); + embeddingsDb.exec(` + CREATE TABLE IF NOT EXISTS embeddings ( + file_path TEXT PRIMARY KEY, + mtime REAL NOT NULL, + model TEXT NOT NULL, + embedding BLOB NOT NULL + ) + `); + } + return embeddingsDb; +} +function getCachedEmbedding(filePath, mtime) { + const db = getEmbeddingsDb(); + const model = appSettings.embedding.model || "text-embedding-3-small"; + const row = db.prepare("SELECT embedding FROM embeddings WHERE file_path = ? AND mtime = ? AND model = ?").get(filePath, mtime, model); + if (!row) + return null; + return Array.from(new Float64Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 8)); +} +function setCachedEmbedding(filePath, mtime, embedding) { + const db = getEmbeddingsDb(); + const model = appSettings.embedding.model || "text-embedding-3-small"; + const buf = Buffer.from(new Float64Array(embedding).buffer); + db.prepare("INSERT OR REPLACE INTO embeddings (file_path, mtime, model, embedding) VALUES (?, ?, ?, ?)").run(filePath, mtime, model, buf); +} +async function getFileEmbeddings(files, workspace) { + const result = new Map(); + const toEmbed = []; + for (const relPath of files) { + const full = path.join(workspace, relPath); + try { + const stat = fs.statSync(full); + const cached = getCachedEmbedding(relPath, stat.mtimeMs); + if (cached) { + result.set(relPath, cached); + } + else { + toEmbed.push(relPath); + } + } + catch { /* skip */ } + } + // Embed uncached files (max 20 per request to stay responsive) + for (const relPath of toEmbed.slice(0, 20)) { + const full = path.join(workspace, relPath); + try { + const content = fs.readFileSync(full, "utf-8").substring(0, 2000); + const emb = await getEmbedding(content); + if (emb) { + const stat = fs.statSync(full); + setCachedEmbedding(relPath, stat.mtimeMs, emb); + result.set(relPath, emb); + } + } + catch { /* skip */ } + } + return result; +} +app.get("/api/semantic-search", async (c) => { + const { workspace } = getAgentFromQuery(c); + const q = (c.req.query("q") || "").trim(); + const mode = c.req.query("mode") || "bm25"; // bm25 | vector + if (!q || q.length < 2) + return c.json([]); + // BM25 mode: use QMD + if (mode === "bm25" && qmdAvailable) { + try { + const { stdout } = await exec(`export PATH="$HOME/.bun/bin:$PATH" && qmd search ${JSON.stringify(q)} -n 10 --json`, { timeout: 20000 }); + const raw = JSON.parse(stdout); + return c.json(raw.map((r) => ({ + path: qmdUriToRelPath(r.file), + title: r.title, + snippet: r.snippet.replace(/@@ [^@]+ @@[^\n]*\n?/, "").substring(0, 300), + score: Math.round(r.score * 100), + }))); + } + catch (e) { + console.error("BM25 search error:", e.message); + return c.json([]); + } + } + // Vector mode: use embedding API + if (mode === "vector" && appSettings.embedding.enabled && appSettings.embedding.apiKey) { + try { + const queryEmb = await getEmbedding(q); + if (!queryEmb) + return c.json([]); + const files = collectMdFiles(workspace); + const fileEmbs = await getFileEmbeddings(files, workspace); + const scored = []; + for (const [filePath, emb] of fileEmbs) { + const sim = cosineSim(queryEmb, emb); + const full = path.join(workspace, filePath); + let content = ""; + try { + content = fs.readFileSync(full, "utf-8"); + } + catch { + continue; + } + const firstLine = content.split("\n").find(l => l.startsWith("#"))?.replace(/^#+\s*/, "") || filePath; + scored.push({ + path: filePath, + score: Math.round(sim * 100), + title: firstLine, + snippet: content.substring(0, 300).replace(/\n/g, " "), + }); + } + scored.sort((a, b) => b.score - a.score); + return c.json(scored.slice(0, 10)); + } + catch (e) { + console.error("Vector search error:", e.message); + return c.json([]); + } + } + return c.json([]); +}); +// Extract tags from content: ## headers and #hashtags +function extractTags(content) { + const tags = new Set(); + // Extract ## headers + const headers = content.match(/^##\s+(.+)$/gm) || []; + headers.forEach(h => { + const tag = h.replace(/^##\s+/, "").replace(/[*_`]/g, "").trim(); + if (tag.length < 30 && tag.length > 0) + tags.add(tag); + }); + // Extract #hashtags (but not markdown headers) + const hashtags = content.match(/(? { + const tag = h.replace(/^#/, "").trim(); + if (tag.length < 30 && tag.length > 0) + tags.add(tag); + }); + return Array.from(tags); +} +// Scan all markdown files and extract tags +function scanAllTags(workspace) { + const tagMap = new Map(); + const mdFiles = collectMdFiles(workspace); + for (const relPath of mdFiles) { + const fullPath = path.join(workspace, relPath); + try { + const content = fs.readFileSync(fullPath, "utf-8"); + const tags = extractTags(content); + for (const tag of tags) { + if (!tagMap.has(tag)) { + tagMap.set(tag, { name: tag, count: 0, files: [] }); + } + const info = tagMap.get(tag); + info.count++; + info.files.push(relPath); + } + } + catch { /* skip */ } + } + return tagMap; +} +app.get("/api/tags", (c) => { + const { workspace } = getAgentFromQuery(c); + const tagMap = scanAllTags(workspace); + const tags = Array.from(tagMap.values()).sort((a, b) => b.count - a.count); + return c.json(tags); +}); +app.get("/api/files-by-tag/:tag", (c) => { + const { workspace } = getAgentFromQuery(c); + const tagParam = decodeURIComponent(c.req.param("tag")); + const tagMap = scanAllTags(workspace); + const tagInfo = tagMap.get(tagParam); + if (!tagInfo) { + return c.json([]); + } + const results = []; + for (const relPath of tagInfo.files) { + const fullPath = path.join(workspace, relPath); + try { + const content = fs.readFileSync(fullPath, "utf-8"); + const clean = content.replace(/^---[\s\S]*?---/, "").trim(); + const titleMatch = clean.match(/^#\s+(.+)$/m); + const title = titleMatch ? titleMatch[1].trim() : path.basename(relPath, ".md"); + const lines = clean.split("\n").filter(l => l.trim() && !l.startsWith("#")); + let preview = lines.slice(0, 2).join(" ").replace(/[*_`\[\]]/g, "").trim(); + if (preview.length > 120) + preview = preview.slice(0, 120) + "…"; + const date = relPath.match(/(\d{4}-\d{2}-\d{2})/)?.[1] || ""; + const fileTags = extractTags(content); + results.push({ + path: relPath, + title, + preview: preview || "(空)", + date, + tags: fileTags, + }); + } + catch { /* skip */ } + } + // Sort by date (newest first) + results.sort((a, b) => b.date.localeCompare(a.date)); + return c.json(results); +}); +app.get("/api/timeline", (c) => { + const { workspace } = getAgentFromQuery(c); + const memoryDir = path.join(workspace, "memory"); + const results = []; + function scanDir(dir, rel) { + let entries; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } + catch { + return; + } + for (const e of entries) { + if (e.isDirectory()) { + scanDir(path.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name); + } + else if (e.isFile() && /^\d{4}-\d{2}-\d{2}(-\w+)?\.md$/.test(e.name)) { + const filePath = `memory/${rel ? rel + "/" : ""}${e.name}`; + const fullPath = path.join(dir, e.name); + try { + const content = fs.readFileSync(fullPath, "utf-8"); + const date = e.name.match(/(\d{4}-\d{2}-\d{2})/)?.[1] || ""; + const clean = content.replace(/^---[\s\S]*?---/, "").trim(); + const titleMatch = clean.match(/^#\s+(.+)$/m); + const title = titleMatch ? titleMatch[1].trim() : e.name.replace(/\.md$/, ""); + const lines = clean.split("\n").filter(l => l.trim() && !l.startsWith("#")); + let preview = lines.slice(0, 2).join(" ").replace(/[*_`\[\]]/g, "").trim(); + if (preview.length > 120) + preview = preview.slice(0, 120) + "…"; + const headers = content.match(/^##\s+(.+)$/gm) || []; + const tags = headers.map(h => h.replace(/^##\s+/, "").replace(/[*_`]/g, "").trim()).filter(t => t.length < 20).slice(0, 4); + results.push({ date, path: filePath, title, preview: preview || "(空)", tags, charCount: content.length }); + } + catch { /* skip */ } + } + } + } + scanDir(memoryDir, ""); + results.sort((a, b) => b.date.localeCompare(a.date)); + return c.json(results); +}); +app.get("/api/recent", (c) => { + const { workspace } = getAgentFromQuery(c); + const files = collectMdFiles(workspace); + const withStats = files.map((relPath) => { + const full = path.join(workspace, relPath); + try { + const stat = fs.statSync(full); + return { path: relPath, mtime: stat.mtime.getTime(), size: stat.size }; + } + catch { + return null; + } + }).filter(Boolean); + withStats.sort((a, b) => b.mtime - a.mtime); + const limit = Math.min(Number(c.req.query("limit")) || 10, 50); + return c.json(withStats.slice(0, limit)); +}); +app.get("/api/stats/monthly", (c) => { + const { workspace } = getAgentFromQuery(c); + const memoryDir = path.join(workspace, "memory"); + const counts = {}; + let entries; + try { + entries = fs.readdirSync(memoryDir, { withFileTypes: true }); + } + catch { + return c.json([]); + } + for (const entry of entries) { + if (!entry.isFile() || !entry.name.endsWith(".md")) + continue; + const match = entry.name.match(/^(\d{4}-\d{2})/); + if (match) { + counts[match[1]] = (counts[match[1]] || 0) + 1; + } + else { + try { + const stat = fs.statSync(path.join(memoryDir, entry.name)); + const month = stat.mtime.toISOString().slice(0, 7); + counts[month] = (counts[month] || 0) + 1; + } + catch { /* skip */ } + } + } + const result = Object.entries(counts) + .map(([month, count]) => ({ month, count })) + .sort((a, b) => a.month.localeCompare(b.month)); + return c.json(result); +}); +app.get("/api/stats/daily", (c) => { + const { workspace } = getAgentFromQuery(c); + const memoryDir = path.join(workspace, "memory"); + const results = []; + let entries; + try { + entries = fs.readdirSync(memoryDir, { withFileTypes: true }); + } + catch { + return c.json([]); + } + const dateMap = new Map(); + for (const entry of entries) { + if (!entry.isFile() || !entry.name.endsWith(".md")) + continue; + const match = entry.name.match(/^(\d{4}-\d{2}-\d{2})/); + if (!match) + continue; + const date = match[1]; + try { + const stat = fs.statSync(path.join(memoryDir, entry.name)); + const existing = dateMap.get(date); + if (existing) { + existing.count++; + existing.size += stat.size; + } + else { + dateMap.set(date, { count: 1, size: stat.size }); + } + } + catch { /* skip */ } + } + for (const [date, val] of dateMap) { + results.push({ date, count: val.count, size: val.size }); + } + results.sort((a, b) => a.date.localeCompare(b.date)); + return c.json(results); +}); +app.get("/api/info", (c) => { + const { workspace } = getAgentFromQuery(c); + let name = "Unknown Bot"; + let description = ""; + for (const fname of ["IDENTITY.md", "SOUL.md"]) { + const fpath = path.join(workspace, fname); + if (fs.existsSync(fpath)) { + const content = fs.readFileSync(fpath, "utf-8"); + const heading = content.match(/^#\s+(.+)/m); + if (heading) + name = heading[1].trim(); + const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#")); + if (lines.length > 0) + description = lines[0].trim().substring(0, 200); + break; + } + } + return c.json({ name, version: "1.0.0", description }); +}); +app.get("/api/system", (c) => { + const { workspace } = getAgentFromQuery(c); + const uptime = os.uptime(); + const memTotal = os.totalmem(); + const memFree = os.freemem(); + const load = os.loadavg(); + const platform = `${os.platform()} ${os.release()}`; + const hostname = os.hostname(); + const today = new Date().toISOString().split("T")[0]; + const todayPath = path.join(workspace, "memory", `${today}.md`); + let todayMemory = null; + if (fs.existsSync(todayPath)) { + const content = fs.readFileSync(todayPath, "utf-8"); + todayMemory = { + filename: `memory/${today}.md`, + snippet: content.split("\n").slice(0, 10).join("\n"), + length: content.length, + }; + } + const totalFiles = collectMdFiles(workspace).length; + return c.json({ + uptime, memTotal, memFree, memUsed: memTotal - memFree, + load, platform, hostname, todayMemory, totalFiles, + }); +}); +app.get("/api/agent/status", async (c) => { + // 1. Config + const home = os.homedir(); + const configPath = path.join(home, ".openclaw", "openclaw.json"); + let safeConfig = {}; + try { + if (fs.existsSync(configPath)) { + const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")); + // Whitelist specific fields + safeConfig = { + version: raw.version, + update: raw.update, + models: { mode: raw.models?.mode }, + agents: { defaults: raw.agents?.defaults }, + gateway: { + port: raw.gateway?.port, + mode: raw.gateway?.mode, + }, + }; + } + } + catch (e) { + console.error("Failed to read config", e); + safeConfig = { error: "Could not read config" }; + } + // 2. Gateway Status + let gatewayStatus = null; + try { + const { stdout } = await exec("openclaw gateway status --json"); + gatewayStatus = JSON.parse(stdout); + } + catch (e) { + // console.error("Failed to get gateway status", e); + // fallback or null + } + // 3. Heartbeat + let heartbeat = null; + try { + const hbPath = path.join(DEFAULT_WORKSPACE, "memory", "heartbeat-state.json"); + if (fs.existsSync(hbPath)) { + heartbeat = JSON.parse(fs.readFileSync(hbPath, "utf-8")); + } + } + catch (e) { + console.error("Failed to read heartbeat", e); + } + return c.json({ + config: safeConfig, + gateway: gatewayStatus, + heartbeat + }); +}); +// --------------------------------------------------------------------------- +// Gateway Chat Proxy +// --------------------------------------------------------------------------- +app.post("/api/gateway/chat", async (c) => { + const { gatewayUrl, token, messages } = await c.req.json(); + if (!gatewayUrl || !token || !messages) { + return c.json({ error: "Missing gatewayUrl, token, or messages" }, 400); + } + try { + const url = `${gatewayUrl.replace(/\/+$/, "")}/v1/chat/completions`; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 30000); + const resp = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ model: "default", messages }), + signal: controller.signal, + }); + clearTimeout(timeout); + if (!resp.ok) { + const text = await resp.text(); + return c.json({ error: text }, resp.status); + } + const data = await resp.json(); + return c.json(data); + } + catch (err) { + if (err.name === "AbortError") { + return c.json({ error: "Gateway request timeout (30s)" }, 504); + } + return c.json({ error: err.message || "Gateway request failed" }, 502); + } +}); +// --------------------------------------------------------------------------- +// AI Summarize +// --------------------------------------------------------------------------- +const GATEWAY_CHAT_URL = process.env.GATEWAY_CHAT_URL || "http://silicon-01:3001/v1/chat/completions"; +const SUMMARIZE_MODEL = process.env.SUMMARIZE_MODEL || "kimi-k2.5"; +app.post("/api/summarize", async (c) => { + const { workspace } = getAgentFromQuery(c); + const { path: filePath, content: providedContent, save } = await c.req.json(); + const full = safePath(filePath, workspace); + if (!full) + return c.json({ error: "Invalid path" }, 400); + let content = providedContent; + if (!content) { + if (!fs.existsSync(full)) + return c.json({ error: "Not found" }, 404); + content = fs.readFileSync(full, "utf-8"); + } + // Strip existing frontmatter for summarization + const bodyMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/); + const body = bodyMatch ? bodyMatch[1] : content; + if (body.trim().length < 50) { + return c.json({ error: "Content too short to summarize" }, 400); + } + try { + const resp = await fetch(GATEWAY_CHAT_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: SUMMARIZE_MODEL, + messages: [ + { + role: "system", + content: "You are a concise summarizer. Given a markdown document, produce a brief summary (1-3 sentences, max 200 chars). Reply with ONLY the summary text, no quotes, no prefix.", + }, + { role: "user", content: body.slice(0, 8000) }, + ], + max_tokens: 256, + temperature: 0.3, + }), + signal: AbortSignal.timeout(30000), + }); + if (!resp.ok) { + const text = await resp.text(); + return c.json({ error: `Gateway error: ${text}` }, 502); + } + const data = await resp.json(); + const summary = (data.choices?.[0]?.message?.content || "").trim(); + if (!summary) + return c.json({ error: "Empty summary returned" }, 502); + // Optionally save to file frontmatter + if (save) { + const existingFm = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); + let newContent; + if (existingFm) { + // Update or add summary field in existing frontmatter + const fmContent = existingFm[1]; + if (/^summary:/m.test(fmContent)) { + const updatedFm = fmContent.replace(/^summary:.*$/m, `summary: "${summary.replace(/"/g, '\\"')}"`); + newContent = content.replace(existingFm[0], `---\n${updatedFm}\n---`); + } + else { + newContent = content.replace(existingFm[0], `---\n${fmContent}\nsummary: "${summary.replace(/"/g, '\\"')}"\n---`); + } + } + else { + newContent = `---\nsummary: "${summary.replace(/"/g, '\\"')}"\n---\n${content}`; + } + fs.writeFileSync(full, newContent, "utf-8"); + const stat = fs.statSync(full); + return c.json({ summary, saved: true, mtime: stat.mtime }); + } + return c.json({ summary, saved: false }); + } + catch (err) { + if (err.name === "AbortError" || err.name === "TimeoutError") { + return c.json({ error: "Gateway timeout (30s)" }, 504); + } + return c.json({ error: err.message || "Summarize failed" }, 502); + } +}); +// --------------------------------------------------------------------------- +// WebSocket — live file change notifications +// --------------------------------------------------------------------------- +const wsClients = new Set(); +app.get("/ws", upgradeWebSocket(() => ({ + onOpen(_event, ws) { + wsClients.add(ws); + }, + onClose(_event, ws) { + wsClients.delete(ws); + }, +}))); +function broadcast(data) { + const msg = JSON.stringify(data); + for (const ws of wsClients) { + try { + ws.send(msg); + } + catch { /* ignore */ } + } +} +const watcher = watch(path.join(DEFAULT_WORKSPACE, "**/*.md"), { + ignoreInitial: true, + ignored: /(^|[/\\])\.(git|node_modules)/, + awaitWriteFinish: { stabilityThreshold: 300 }, +}); +watcher.on("all", (event, filePath) => { + const rel = path.relative(DEFAULT_WORKSPACE, filePath); + broadcast({ type: "file-change", event, path: rel }); +}); +// --------------------------------------------------------------------------- +// Workspace assets (images, SVGs, etc.) +// --------------------------------------------------------------------------- +app.get("/workspace-assets/*", async (c) => { + const { workspace } = getAgentFromQuery(c); + const assetPath = c.req.path.replace("/workspace-assets/", ""); + const fullPath = path.join(workspace, "assets", assetPath); + if (!fullPath.startsWith(path.join(workspace, "assets"))) { + return c.json({ error: "Invalid path" }, 403); + } + if (!fs.existsSync(fullPath)) { + return c.json({ error: "Not found" }, 404); + } + const ext = path.extname(fullPath).toLowerCase(); + const mimeTypes = { + ".svg": "image/svg+xml", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + }; + const contentType = mimeTypes[ext] || "application/octet-stream"; + const content = fs.readFileSync(fullPath); + c.header("Content-Type", contentType); + c.header("Cache-Control", "public, max-age=3600"); + return c.body(content); +}); +// --------------------------------------------------------------------------- +// Static file serving +// --------------------------------------------------------------------------- +if (fs.existsSync(STATIC_DIR)) { + app.use("/assets/*", serveStatic({ + root: STATIC_DIR, + rewriteRequestPath: (p) => p, + })); + app.use("*", serveStatic({ root: STATIC_DIR })); + // SPA fallback + app.get("*", (c) => { + const html = fs.readFileSync(path.join(STATIC_DIR, "index.html"), "utf-8"); + c.header("Cache-Control", "no-cache, no-store, must-revalidate"); + return c.html(html); + }); +} +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- +if (process.env.NODE_ENV !== 'test') { + const server = serve({ fetch: app.fetch, port: PORT, hostname: "0.0.0.0" }, (info) => { + console.log(`📝 Memory Viewer running at http://localhost:${info.port}`); + console.log(`📂 Default Workspace: ${DEFAULT_WORKSPACE}`); + }); + injectWebSocket(server); +} diff --git a/memory-viewer/server/api.test.ts b/memory-viewer/server/api.test.ts new file mode 100644 index 0000000..5eda531 --- /dev/null +++ b/memory-viewer/server/api.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, beforeAll, afterAll } from 'vitest'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import { app } from './index.js'; + +describe('Server API', () => { + it('GET /api/info returns bot info', async () => { + // Note: This relies on actual file system unless mocked. + // Ideally we mock fs, but for integration test on CI it's fine. + const res = await app.request('/api/info'); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data).toHaveProperty('version'); + }); + + it('GET /api/agent/status returns structure', async () => { + const res = await app.request('/api/agent/status'); + expect(res.status).toBe(200); + const data = await res.json(); + expect(data).toHaveProperty('config'); + expect(data).toHaveProperty('gateway'); + }); +}); + +describe('Backlinks API', () => { + // These tests rely on the actual WORKSPACE directory (~/clawd by default) + // They test the API structure and basic behavior + + it('GET /api/resolve-wikilink returns 400 without link', async () => { + const res = await app.request('/api/resolve-wikilink'); + expect(res.status).toBe(400); + }); + + it('GET /api/resolve-wikilink resolves existing file', async () => { + // MEMORY.md should exist in the workspace + const res = await app.request('/api/resolve-wikilink?link=MEMORY'); + expect(res.status).toBe(200); + const data = await res.json() as any; + expect(data).toHaveProperty('found'); + expect(data).toHaveProperty('path'); + }); + + it('GET /api/resolve-wikilink returns not found for nonexistent', async () => { + const res = await app.request('/api/resolve-wikilink?link=nonexistent-file-that-does-not-exist-12345'); + expect(res.status).toBe(200); + const data = await res.json() as any; + expect(data.found).toBe(false); + }); +}); diff --git a/memory-viewer/server/cron-trigger.mjs b/memory-viewer/server/cron-trigger.mjs new file mode 100644 index 0000000..99a09db --- /dev/null +++ b/memory-viewer/server/cron-trigger.mjs @@ -0,0 +1,99 @@ +#!/usr/bin/env node +// Trigger a cron job via OpenClaw gateway WebSocket +// Usage: node cron-trigger.mjs +import { readFileSync } from "fs"; +import { homedir } from "os"; +import { join } from "path"; +import { randomUUID } from "crypto"; +import WebSocket from "ws"; + +const jobId = process.argv[2]; +if (!jobId) { console.log(JSON.stringify({success:false, error:"missing jobId"})); process.exit(1); } + +const configPath = join(homedir(), ".openclaw", "openclaw.json"); +const config = JSON.parse(readFileSync(configPath, "utf-8")); +const port = config.gateway?.port || 18789; +const token = config.gateway?.auth?.token || ""; + +const ws = new WebSocket(`ws://127.0.0.1:${port}`); +let connectNonce = null; +let connected = false; +const pending = new Map(); + +const timeout = setTimeout(() => { + console.log(JSON.stringify({success:false, error:"timeout"})); + ws.close(); + process.exit(1); +}, 10000); + +function send(obj) { ws.send(JSON.stringify(obj)); } + +function request(method, params) { + const id = randomUUID(); + return new Promise((resolve, reject) => { + pending.set(id, { resolve, reject }); + send({ type: "req", id, method, params }); + }); +} + +ws.on("message", (data) => { + try { + const msg = JSON.parse(data.toString()); + + // Event frame + if (msg.type === "event") { + if (msg.event === "connect.challenge") { + connectNonce = msg.payload?.nonce; + // Send connect request + request("connect", { + minProtocol: 3, + maxProtocol: 3, + client: { + id: "gateway-client", + displayName: "Memory Viewer", + version: "1.0.0", + platform: "linux", + mode: "backend", + }, + caps: [], + auth: { token }, + role: "operator", + scopes: ["operator.admin"], + }).then(() => { + connected = true; + // Send cron.run — it's async, so fire and consider it triggered + const cronId = randomUUID(); + ws.send(JSON.stringify({type:'req', id:cronId, method:'cron.run', params:{id: jobId}})); + // Give it a moment to dispatch, then report success + setTimeout(() => { + clearTimeout(timeout); + console.log(JSON.stringify({success:true, result:'triggered'})); + ws.close(); + process.exit(0); + }, 500); + }).catch((err) => { + clearTimeout(timeout); + console.log(JSON.stringify({success:false, error: err.message})); + ws.close(); + process.exit(1); + }); + } + return; + } + + // Response frame + if (msg.type === "res") { + const p = pending.get(msg.id); + if (!p) return; + pending.delete(msg.id); + if (msg.ok) p.resolve(msg.payload); + else p.reject(new Error(msg.error?.message || "unknown error")); + } + } catch {} +}); + +ws.on("error", (err) => { + clearTimeout(timeout); + console.log(JSON.stringify({success:false, error: err.message})); + process.exit(1); +}); diff --git a/memory-viewer/server/index.ts b/memory-viewer/server/index.ts new file mode 100644 index 0000000..ab0289e --- /dev/null +++ b/memory-viewer/server/index.ts @@ -0,0 +1,1466 @@ +/** + * Memory Viewer — API Server (Hono) + * + * Provides REST endpoints for browsing, reading, editing, and searching + * Markdown files, plus a WebSocket channel that pushes live file-change + * notifications to connected clients. + */ +import { Hono } from "hono"; +import { compress } from "hono/compress"; +import { cors } from "hono/cors"; +import { serveStatic } from "@hono/node-server/serve-static"; +import { createNodeWebSocket } from "@hono/node-ws"; +import { serve } from "@hono/node-server"; +import fs from "fs"; +import path from "path"; +import os from "os"; +import Database from "better-sqlite3"; +import { exec as execCallback } from "child_process"; +import util from "util"; +import { watch } from "chokidar"; +import type { WSContext } from "hono/ws"; + +const exec = util.promisify(execCallback); + +// --------------------------------------------------------------------------- +// Agent Config Types +// --------------------------------------------------------------------------- +interface AgentConfig { + id: string; + name: string; + workspace?: string; + agentDir?: string; + identity?: { + name?: string; + emoji?: string; + }; + skills?: string[]; +} + +interface AgentsConfig { + defaults: { + workspace?: string; + }; + list: AgentConfig[]; +} + +interface OpenClawConfig { + agents?: AgentsConfig; +} + +interface AgentInfo { + id: string; + name: string; + workspace: string; + emoji: string; + skills?: string[]; +} + +// --------------------------------------------------------------------------- +// Settings +// --------------------------------------------------------------------------- +const SETTINGS_DIR = path.join(os.homedir(), ".config", "memory-viewer"); +const SETTINGS_FILE = path.join(SETTINGS_DIR, "settings.json"); + +interface AppSettings { + embedding: { + enabled: boolean; + apiUrl: string; + apiKey: string; + model: string; + }; + pluginsDir: string; +} + +const DEFAULT_SETTINGS: AppSettings = { + embedding: { enabled: false, apiUrl: "", apiKey: "", model: "" }, + pluginsDir: "", +}; + +function loadSettings(): AppSettings { + try { + const raw = fs.readFileSync(SETTINGS_FILE, "utf-8"); + return { ...DEFAULT_SETTINGS, ...JSON.parse(raw) }; + } catch { + return { ...DEFAULT_SETTINGS }; + } +} + +function saveSettings(s: AppSettings): void { + fs.mkdirSync(SETTINGS_DIR, { recursive: true }); + fs.writeFileSync(SETTINGS_FILE, JSON.stringify(s, null, 2)); +} + +let appSettings = loadSettings(); + +// --------------------------------------------------------------------------- +// Config +// --------------------------------------------------------------------------- +const PORT = Number(process.env.PORT) || 3001; +const DEFAULT_WORKSPACE = process.env.WORKSPACE_DIR || path.join(os.homedir(), "clawd"); +const STATIC_DIR = process.env.STATIC_DIR || path.join(import.meta.dirname, "..", "dist"); + +const app = new Hono(); +export { app }; // Export for testing +const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }); + +app.use("*", compress()); +app.use("*", cors({ origin: "*" })); + +// --------------------------------------------------------------------------- +// Agent Management +// --------------------------------------------------------------------------- +const OPENCLAW_CONFIG_PATH = path.join(os.homedir(), ".openclaw", "openclaw.json"); + +function loadOpenClawConfig(): OpenClawConfig | null { + try { + if (fs.existsSync(OPENCLAW_CONFIG_PATH)) { + const raw = fs.readFileSync(OPENCLAW_CONFIG_PATH, "utf-8"); + return JSON.parse(raw); + } + } catch (e) { + console.error("Failed to load OpenClaw config:", e); + } + return null; +} + +function getAgentWorkspace(agentConfig: AgentConfig, defaults: { workspace?: string }): string { + // Priority: workspace > agentDir > defaults.workspace > DEFAULT_WORKSPACE + if (agentConfig.workspace) { + return agentConfig.workspace; + } + if (agentConfig.agentDir) { + return agentConfig.agentDir; + } + if (defaults.workspace) { + return defaults.workspace; + } + return DEFAULT_WORKSPACE; +} + +function getAgents(): AgentInfo[] { + const config = loadOpenClawConfig(); + if (!config?.agents?.list) { + // Return default agent if no config + return [{ + id: "default", + name: "Default Agent", + workspace: DEFAULT_WORKSPACE, + emoji: "🤖", + }]; + } + + const defaults = config.agents.defaults || {}; + + return config.agents.list.map((agent) => ({ + id: agent.id, + name: agent.name || agent.id, + workspace: getAgentWorkspace(agent, defaults), + emoji: agent.identity?.emoji || "🤖", + skills: agent.skills || undefined, + })); +} + +function getAgentById(agentId: string): AgentInfo | null { + const agents = getAgents(); + return agents.find((a) => a.id === agentId) || null; +} + +// Get workspace for a given agent ID +function getWorkspaceForAgent(agentId: string | null | undefined): string { + if (!agentId || agentId === "default") { + // Try to find "default" agent in config, otherwise use default workspace + const agent = getAgentById("default"); + if (agent) return agent.workspace; + return DEFAULT_WORKSPACE; + } + + const agent = getAgentById(agentId); + if (agent) return agent.workspace; + + // Fallback to default workspace if agent not found + return DEFAULT_WORKSPACE; +} + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function safePath(filePath: string | undefined | null, workspace: string): string | null { + if (!filePath || filePath.includes("..") || !filePath.endsWith(".md")) return null; + const full = path.resolve(workspace, filePath); + if (!full.startsWith(path.resolve(workspace))) return null; + return full; +} + +interface TreeNode { + name: string; + type: "file" | "dir"; + path: string; + children?: TreeNode[]; +} + +function scanDir(dir: string, prefix = ""): TreeNode[] { + const result: TreeNode[] = []; + let entries: fs.Dirent[]; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return result; + } + for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { + if (entry.name.startsWith(".") || entry.name === "node_modules") continue; + const relPath = prefix ? `${prefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) { + const children = scanDir(path.join(dir, entry.name), relPath); + if (children.length > 0) { + result.push({ name: entry.name, type: "dir", path: relPath, children }); + } + } else if (entry.name.endsWith(".md")) { + result.push({ name: entry.name, type: "file", path: relPath }); + } + } + return result; +} + +function collectMdFiles(dir: string, prefix = ""): string[] { + const files: string[] = []; + let entries: fs.Dirent[]; + try { + entries = fs.readdirSync(dir, { withFileTypes: true }); + } catch { + return files; + } + for (const entry of entries) { + if (entry.name.startsWith(".") || entry.name === "node_modules") continue; + const relPath = prefix ? `${prefix}/${entry.name}` : entry.name; + if (entry.isDirectory()) { + files.push(...collectMdFiles(path.join(dir, entry.name), relPath)); + } else if (entry.name.endsWith(".md")) { + files.push(relPath); + } + } + return files; +} + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// REST API +// --------------------------------------------------------------------------- + +// Get agent from query parameter +function getAgentFromQuery(c: any): { agentId: string; workspace: string } { + const agentId = c.req.query("agent") || "default"; + const workspace = getWorkspaceForAgent(agentId); + return { agentId, workspace }; +} + +// Agents API +app.get("/api/agents", (c) => { + return c.json(getAgents()); +}); + +app.get("/api/skills", (c) => { + const { workspace } = getAgentFromQuery(c); + const skillsDir = path.join(workspace, "skills"); + const results: { id: string; name: string; description: string; path: string }[] = []; + let entries: fs.Dirent[]; + try { + entries = fs.readdirSync(skillsDir, { withFileTypes: true }); + } catch { + return c.json([]); + } + for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) { + if (!entry.isDirectory() || entry.name.startsWith(".")) continue; + const skillMd = path.join(skillsDir, entry.name, "SKILL.md"); + if (!fs.existsSync(skillMd)) continue; + const content = fs.readFileSync(skillMd, "utf-8"); + let name = entry.name; + let description = ""; + const fmMatch = content.match(/^---\s*\n([\s\S]*?)\n---/); + if (fmMatch) { + const nameMatch = fmMatch[1].match(/^name:\s*(.+)/m); + const descMatch = fmMatch[1].match(/^description:\s*(.+)/m); + if (nameMatch) name = nameMatch[1].trim().replace(/^["']|["']$/g, ""); + if (descMatch) description = descMatch[1].trim().replace(/^["']|["']$/g, ""); + } + results.push({ id: entry.name, name, description, path: `skills/${entry.name}/SKILL.md` }); + } + return c.json(results); +}); + +app.get("/api/files", (c) => { + const { workspace } = getAgentFromQuery(c); + return c.json(scanDir(workspace)); +}); + +app.get("/api/file", (c) => { + const { workspace } = getAgentFromQuery(c); + const full = safePath(c.req.query("path"), workspace); + if (!full) return c.json({ error: "Invalid path" }, 400); + if (!fs.existsSync(full)) return c.json({ error: "Not found" }, 404); + const content = fs.readFileSync(full, "utf-8"); + const stat = fs.statSync(full); + return c.json({ content, mtime: stat.mtime, size: stat.size }); +}); + +app.put("/api/file", async (c) => { + const { workspace } = getAgentFromQuery(c); + const { path: filePath, content, expectedMtime } = await c.req.json(); + const full = safePath(filePath, workspace); + if (!full) return c.json({ error: "Invalid path" }, 400); + + if (expectedMtime && fs.existsSync(full)) { + const currentMtime = fs.statSync(full).mtime.toISOString(); + if (currentMtime !== expectedMtime) { + const currentContent = fs.readFileSync(full, "utf-8"); + return c.json({ + error: "conflict", + message: "File was modified since you started editing", + serverMtime: currentMtime, + serverContent: currentContent, + }, 409); + } + } + + fs.mkdirSync(path.dirname(full), { recursive: true }); + fs.writeFileSync(full, content, "utf-8"); + const stat = fs.statSync(full); + return c.json({ ok: true, mtime: stat.mtime }); +}); + +app.get("/api/resolve-wikilink", (c) => { + const { workspace } = getAgentFromQuery(c); + const link = (c.req.query("link") || "").trim(); + if (!link) return c.json({ error: "Missing link parameter" }, 400); + + const allFiles = collectMdFiles(workspace); + + // Try exact path match first + const exactPath = link.endsWith(".md") ? link : `${link}.md`; + if (allFiles.includes(exactPath)) { + return c.json({ found: true, path: exactPath }); + } + + // Try case-insensitive exact path + const exactLower = exactPath.toLowerCase(); + const ciMatch = allFiles.find((f) => f.toLowerCase() === exactLower); + if (ciMatch) { + return c.json({ found: true, path: ciMatch }); + } + + // Try filename-only match (fuzzy) + const linkLower = link.toLowerCase(); + const byName = allFiles.find((f) => { + const name = path.basename(f, ".md"); + return name.toLowerCase() === linkLower; + }); + if (byName) { + return c.json({ found: true, path: byName }); + } + + return c.json({ found: false, path: null }); +}); + +app.get("/api/search", (c) => { + const { workspace } = getAgentFromQuery(c); + const q = (c.req.query("q") || "").trim().toLowerCase(); + if (!q || q.length < 2) return c.json([]); + + const files = collectMdFiles(workspace); + const results: { path: string; matches: { line: number; text: string }[] }[] = []; + + for (const relPath of files) { + const full = path.join(workspace, relPath); + let content: string; + try { + content = fs.readFileSync(full, "utf-8"); + } catch { + continue; + } + const lines = content.split("\n"); + const matches: { line: number; text: string }[] = []; + for (let i = 0; i < lines.length; i++) { + if (lines[i].toLowerCase().includes(q)) { + matches.push({ line: i + 1, text: lines[i].substring(0, 200) }); + if (matches.length >= 5) break; + } + } + if (matches.length > 0) results.push({ path: relPath, matches }); + if (results.length >= 50) break; + } + return c.json(results); +}); + +// QMD availability detection — cached at startup +let qmdAvailable: boolean | null = null; +let qmdHasVectors = false; + +async function detectQmd(): Promise { + try { + const { stdout } = await exec( + `export PATH="$HOME/.bun/bin:$PATH" && qmd status 2>/dev/null`, + { timeout: 5000 } + ); + qmdAvailable = stdout.includes("Documents"); + qmdHasVectors = /Vectors:\s*[1-9]/.test(stdout); + console.log(`🔍 QMD: ${qmdAvailable ? "available" : "not found"}${qmdHasVectors ? " (vectors ready)" : ""}`); + } catch { + qmdAvailable = false; + qmdHasVectors = false; + console.log("🔍 QMD: not installed"); + } +} + +// Run detection on startup +detectQmd(); + +// External plugins directory +const PLUGINS_DIR = appSettings.pluginsDir || process.env.PLUGINS_DIR || ""; + +app.get("/api/plugins", (c) => { + if (!PLUGINS_DIR || !fs.existsSync(PLUGINS_DIR)) return c.json([]); + try { + const plugins: { id: string; name: string; entry: string }[] = []; + for (const dir of fs.readdirSync(PLUGINS_DIR, { withFileTypes: true })) { + if (!dir.isDirectory()) continue; + const manifestPath = path.join(PLUGINS_DIR, dir.name, "plugin.json"); + if (fs.existsSync(manifestPath)) { + const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8")); + plugins.push({ id: manifest.id || dir.name, name: manifest.name || dir.name, entry: manifest.entry || "index.js" }); + } + } + return c.json(plugins); + } catch { return c.json([]); } +}); + +// Serve plugin files +app.get("/api/plugins/:id/*", (c) => { + if (!PLUGINS_DIR) return c.text("No plugins dir", 404); + const pluginId = c.req.param("id"); + const filePath = c.req.path.replace(`/api/plugins/${pluginId}/`, ""); + const full = path.join(PLUGINS_DIR, pluginId, filePath); + if (!full.startsWith(path.join(PLUGINS_DIR, pluginId))) return c.text("Forbidden", 403); + if (!fs.existsSync(full)) return c.text("Not found", 404); + const content = fs.readFileSync(full, "utf-8"); + const ext = path.extname(full); + const ct = ext === ".js" ? "application/javascript" : ext === ".css" ? "text/css" : "text/plain"; + return c.text(content, 200, { "Content-Type": ct }); +}); + +app.get("/api/capabilities", (c) => { + const embeddingReady = appSettings.embedding.enabled && !!appSettings.embedding.apiUrl && !!appSettings.embedding.apiKey; + return c.json({ + qmd: qmdAvailable === true, + qmdBm25: qmdAvailable === true, + qmdVector: qmdHasVectors || embeddingReady, + embeddingApi: embeddingReady, + }); +}); + +// Settings API +app.get("/api/settings", (c) => { + return c.json({ + embedding: { + enabled: appSettings.embedding.enabled, + apiUrl: appSettings.embedding.apiUrl, + apiKeySet: !!appSettings.embedding.apiKey, + model: appSettings.embedding.model, + }, + }); +}); + +app.put("/api/settings", async (c) => { + const body = await c.req.json(); + if (body.embedding) { + appSettings.embedding.enabled = body.embedding.enabled ?? appSettings.embedding.enabled; + appSettings.embedding.apiUrl = body.embedding.apiUrl ?? appSettings.embedding.apiUrl; + appSettings.embedding.model = body.embedding.model ?? appSettings.embedding.model; + if (body.embedding.apiKey) { + appSettings.embedding.apiKey = body.embedding.apiKey; + } + } + saveSettings(appSettings); + return c.json({ ok: true }); +}); + +app.get("/api/settings/embedding-stats", (c) => { + const { workspace } = getAgentFromQuery(c); + try { + const db = getEmbeddingsDb(); + const model = appSettings.embedding.model || "text-embedding-3-small"; + const total = (db.prepare("SELECT COUNT(*) as n FROM embeddings WHERE model = ?").get(model) as any)?.n || 0; + const allFiles = collectMdFiles(workspace).length; + const dbSize = fs.existsSync(EMBEDDINGS_DB_PATH) ? fs.statSync(EMBEDDINGS_DB_PATH).size : 0; + return c.json({ + cachedFiles: total, + totalFiles: allFiles, + coverage: allFiles > 0 ? Math.round((total / allFiles) * 100) : 0, + dbSize, + model, + }); + } catch { + return c.json({ cachedFiles: 0, totalFiles: 0, coverage: 0, dbSize: 0, model: "" }); + } +}); + +app.post("/api/settings/test-embedding", async (c) => { + const { apiUrl, apiKey, model } = await c.req.json(); + const url = apiUrl || appSettings.embedding.apiUrl; + const key = apiKey || appSettings.embedding.apiKey; + const mdl = model || appSettings.embedding.model || "text-embedding-3-small"; + + if (!url) return c.json({ ok: false, message: "API URL is required" }); + + try { + const resp = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(key ? { Authorization: `Bearer ${key}` } : {}), + }, + body: JSON.stringify({ input: "test", model: mdl }), + signal: AbortSignal.timeout(10000), + }); + const data: any = await resp.json(); + if (data.data?.[0]?.embedding) { + const dim = data.data[0].embedding.length; + return c.json({ ok: true, message: `✅ ${dim}维向量` }); + } + return c.json({ ok: false, message: data.error?.message || "Unexpected response" }); + } catch (e: any) { + return c.json({ ok: false, message: e.message || "Connection failed" }); + } +}); + +function qmdUriToRelPath(uri: string): string { + // qmd://clawd-memory/memory/survival.md → memory/survival.md + // qmd://clawd-root/MEMORY.md → MEMORY.md + // All collections index from workspace root, so just strip the qmd://collection/ prefix + const match = uri.match(/^qmd:\/\/[^/]+\/(.+)$/); + return match ? match[1] : uri; +} + +// Embedding API helper +async function getEmbedding(text: string): Promise { + const { apiUrl, apiKey, model } = appSettings.embedding; + if (!apiUrl || !apiKey) return null; + try { + const resp = await fetch(apiUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${apiKey}`, + }, + body: JSON.stringify({ input: text, model: model || "text-embedding-3-small" }), + signal: AbortSignal.timeout(10000), + }); + const data: any = await resp.json(); + return data.data?.[0]?.embedding ?? null; + } catch { + return null; + } +} + +function cosineSim(a: number[], b: number[]): number { + let dot = 0, na = 0, nb = 0; + for (let i = 0; i < a.length; i++) { + dot += a[i] * b[i]; + na += a[i] * a[i]; + nb += b[i] * b[i]; + } + return dot / (Math.sqrt(na) * Math.sqrt(nb) + 1e-10); +} + +// SQLite embedding cache — persistent across restarts +const EMBEDDINGS_DB_PATH = path.join(SETTINGS_DIR, "embeddings.sqlite"); +let embeddingsDb: Database.Database | null = null; + +function getEmbeddingsDb(): Database.Database { + if (!embeddingsDb) { + fs.mkdirSync(SETTINGS_DIR, { recursive: true }); + embeddingsDb = new Database(EMBEDDINGS_DB_PATH); + embeddingsDb.pragma("journal_mode = WAL"); + embeddingsDb.exec(` + CREATE TABLE IF NOT EXISTS embeddings ( + file_path TEXT PRIMARY KEY, + mtime REAL NOT NULL, + model TEXT NOT NULL, + embedding BLOB NOT NULL + ) + `); + } + return embeddingsDb; +} + +function getCachedEmbedding(filePath: string, mtime: number): number[] | null { + const db = getEmbeddingsDb(); + const model = appSettings.embedding.model || "text-embedding-3-small"; + const row = db.prepare("SELECT embedding FROM embeddings WHERE file_path = ? AND mtime = ? AND model = ?").get(filePath, mtime, model) as any; + if (!row) return null; + return Array.from(new Float64Array(row.embedding.buffer, row.embedding.byteOffset, row.embedding.byteLength / 8)); +} + +function setCachedEmbedding(filePath: string, mtime: number, embedding: number[]): void { + const db = getEmbeddingsDb(); + const model = appSettings.embedding.model || "text-embedding-3-small"; + const buf = Buffer.from(new Float64Array(embedding).buffer); + db.prepare("INSERT OR REPLACE INTO embeddings (file_path, mtime, model, embedding) VALUES (?, ?, ?, ?)").run(filePath, mtime, model, buf); +} + +async function getFileEmbeddings(files: string[], workspace: string): Promise> { + const result = new Map(); + const toEmbed: string[] = []; + + for (const relPath of files) { + const full = path.join(workspace, relPath); + try { + const stat = fs.statSync(full); + const cached = getCachedEmbedding(relPath, stat.mtimeMs); + if (cached) { + result.set(relPath, cached); + } else { + toEmbed.push(relPath); + } + } catch { /* skip */ } + } + + // Embed uncached files (max 20 per request to stay responsive) + for (const relPath of toEmbed.slice(0, 20)) { + const full = path.join(workspace, relPath); + try { + const content = fs.readFileSync(full, "utf-8").substring(0, 2000); + const emb = await getEmbedding(content); + if (emb) { + const stat = fs.statSync(full); + setCachedEmbedding(relPath, stat.mtimeMs, emb); + result.set(relPath, emb); + } + } catch { /* skip */ } + } + + return result; +} + +app.get("/api/semantic-search", async (c) => { + const { workspace } = getAgentFromQuery(c); + const q = (c.req.query("q") || "").trim(); + const mode = c.req.query("mode") || "bm25"; // bm25 | vector + if (!q || q.length < 2) return c.json([]); + + // BM25 mode: use QMD + if (mode === "bm25" && qmdAvailable) { + try { + const { stdout } = await exec( + `export PATH="$HOME/.bun/bin:$PATH" && qmd search ${JSON.stringify(q)} -n 10 --json`, + { timeout: 20000 } + ); + const raw: { docid: string; score: number; file: string; title: string; snippet: string }[] = JSON.parse(stdout); + return c.json(raw.map((r) => ({ + path: qmdUriToRelPath(r.file), + title: r.title, + snippet: r.snippet.replace(/@@ [^@]+ @@[^\n]*\n?/, "").substring(0, 300), + score: Math.round(r.score * 100), + }))); + } catch (e: any) { + console.error("BM25 search error:", e.message); + return c.json([]); + } + } + + // Vector mode: use embedding API + if (mode === "vector" && appSettings.embedding.enabled && appSettings.embedding.apiKey) { + try { + const queryEmb = await getEmbedding(q); + if (!queryEmb) return c.json([]); + + const files = collectMdFiles(workspace); + const fileEmbs = await getFileEmbeddings(files, workspace); + + const scored: { path: string; score: number; snippet: string; title: string }[] = []; + for (const [filePath, emb] of fileEmbs) { + const sim = cosineSim(queryEmb, emb); + const full = path.join(workspace, filePath); + let content = ""; + try { content = fs.readFileSync(full, "utf-8"); } catch { continue; } + const firstLine = content.split("\n").find(l => l.startsWith("#"))?.replace(/^#+\s*/, "") || filePath; + scored.push({ + path: filePath, + score: Math.round(sim * 100), + title: firstLine, + snippet: content.substring(0, 300).replace(/\n/g, " "), + }); + } + + scored.sort((a, b) => b.score - a.score); + return c.json(scored.slice(0, 10)); + } catch (e: any) { + console.error("Vector search error:", e.message); + return c.json([]); + } + } + + return c.json([]); +}); + +// ============================================================================ +// Tags API - Extract and manage tags from markdown files +// ============================================================================ + +interface TagInfo { + name: string; + count: number; + files: string[]; +} + +interface FileWithTags { + path: string; + title: string; + preview: string; + date: string; + tags: string[]; +} + +// Extract tags from content: ## headers and #hashtags +function extractTags(content: string): string[] { + const tags = new Set(); + + // Extract ## headers + const headers = content.match(/^##\s+(.+)$/gm) || []; + headers.forEach(h => { + const tag = h.replace(/^##\s+/, "").replace(/[*_`]/g, "").trim(); + if (tag.length < 30 && tag.length > 0) tags.add(tag); + }); + + // Extract #hashtags (but not markdown headers) + const hashtags = content.match(/(? { + const tag = h.replace(/^#/, "").trim(); + if (tag.length < 30 && tag.length > 0) tags.add(tag); + }); + + return Array.from(tags); +} + +// Scan all markdown files and extract tags +function scanAllTags(workspace: string): Map { + const tagMap = new Map(); + const mdFiles = collectMdFiles(workspace); + + for (const relPath of mdFiles) { + const fullPath = path.join(workspace, relPath); + try { + const content = fs.readFileSync(fullPath, "utf-8"); + const tags = extractTags(content); + + for (const tag of tags) { + if (!tagMap.has(tag)) { + tagMap.set(tag, { name: tag, count: 0, files: [] }); + } + const info = tagMap.get(tag)!; + info.count++; + info.files.push(relPath); + } + } catch { /* skip */ } + } + + return tagMap; +} + +app.get("/api/tags", (c) => { + const { workspace } = getAgentFromQuery(c); + const tagMap = scanAllTags(workspace); + const tags = Array.from(tagMap.values()).sort((a, b) => b.count - a.count); + return c.json(tags); +}); + +app.get("/api/files-by-tag/:tag", (c) => { + const { workspace } = getAgentFromQuery(c); + const tagParam = decodeURIComponent(c.req.param("tag")); + const tagMap = scanAllTags(workspace); + const tagInfo = tagMap.get(tagParam); + + if (!tagInfo) { + return c.json([]); + } + + const results: FileWithTags[] = []; + for (const relPath of tagInfo.files) { + const fullPath = path.join(workspace, relPath); + try { + const content = fs.readFileSync(fullPath, "utf-8"); + const clean = content.replace(/^---[\s\S]*?---/, "").trim(); + const titleMatch = clean.match(/^#\s+(.+)$/m); + const title = titleMatch ? titleMatch[1].trim() : path.basename(relPath, ".md"); + const lines = clean.split("\n").filter(l => l.trim() && !l.startsWith("#")); + let preview = lines.slice(0, 2).join(" ").replace(/[*_`\[\]]/g, "").trim(); + if (preview.length > 120) preview = preview.slice(0, 120) + "…"; + const date = relPath.match(/(\d{4}-\d{2}-\d{2})/)?.[1] || ""; + const fileTags = extractTags(content); + + results.push({ + path: relPath, + title, + preview: preview || "(空)", + date, + tags: fileTags, + }); + } catch { /* skip */ } + } + + // Sort by date (newest first) + results.sort((a, b) => b.date.localeCompare(a.date)); + return c.json(results); +}); + +app.get("/api/timeline", (c) => { + const { workspace } = getAgentFromQuery(c); + const memoryDir = path.join(workspace, "memory"); + const results: { date: string; path: string; title: string; preview: string; tags: string[]; charCount: number }[] = []; + + function scanDir(dir: string, rel: string) { + let entries: fs.Dirent[]; + try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; } + for (const e of entries) { + if (e.isDirectory()) { + scanDir(path.join(dir, e.name), rel ? `${rel}/${e.name}` : e.name); + } else if (e.isFile() && /^\d{4}-\d{2}-\d{2}(-\w+)?\.md$/.test(e.name)) { + const filePath = `memory/${rel ? rel + "/" : ""}${e.name}`; + const fullPath = path.join(dir, e.name); + try { + const content = fs.readFileSync(fullPath, "utf-8"); + const date = e.name.match(/(\d{4}-\d{2}-\d{2})/)?.[1] || ""; + const clean = content.replace(/^---[\s\S]*?---/, "").trim(); + const titleMatch = clean.match(/^#\s+(.+)$/m); + const title = titleMatch ? titleMatch[1].trim() : e.name.replace(/\.md$/, ""); + const lines = clean.split("\n").filter(l => l.trim() && !l.startsWith("#")); + let preview = lines.slice(0, 2).join(" ").replace(/[*_`\[\]]/g, "").trim(); + if (preview.length > 120) preview = preview.slice(0, 120) + "…"; + const headers = content.match(/^##\s+(.+)$/gm) || []; + const tags = headers.map(h => h.replace(/^##\s+/, "").replace(/[*_`]/g, "").trim()).filter(t => t.length < 20).slice(0, 4); + results.push({ date, path: filePath, title, preview: preview || "(空)", tags, charCount: content.length }); + } catch { /* skip */ } + } + } + } + + scanDir(memoryDir, ""); + results.sort((a, b) => b.date.localeCompare(a.date)); + return c.json(results); +}); + +app.get("/api/recent", (c) => { + const { workspace } = getAgentFromQuery(c); + const files = collectMdFiles(workspace); + const withStats = files.map((relPath) => { + const full = path.join(workspace, relPath); + try { + const stat = fs.statSync(full); + return { path: relPath, mtime: stat.mtime.getTime(), size: stat.size }; + } catch { + return null; + } + }).filter(Boolean) as { path: string; mtime: number; size: number }[]; + withStats.sort((a, b) => b.mtime - a.mtime); + const limit = Math.min(Number(c.req.query("limit")) || 10, 50); + return c.json(withStats.slice(0, limit)); +}); + +app.get("/api/stats/monthly", (c) => { + const { workspace } = getAgentFromQuery(c); + const memoryDir = path.join(workspace, "memory"); + const counts: Record = {}; + let entries: fs.Dirent[]; + try { + entries = fs.readdirSync(memoryDir, { withFileTypes: true }); + } catch { + return c.json([]); + } + for (const entry of entries) { + if (!entry.isFile() || !entry.name.endsWith(".md")) continue; + const match = entry.name.match(/^(\d{4}-\d{2})/); + if (match) { + counts[match[1]] = (counts[match[1]] || 0) + 1; + } else { + try { + const stat = fs.statSync(path.join(memoryDir, entry.name)); + const month = stat.mtime.toISOString().slice(0, 7); + counts[month] = (counts[month] || 0) + 1; + } catch { /* skip */ } + } + } + const result = Object.entries(counts) + .map(([month, count]) => ({ month, count })) + .sort((a, b) => a.month.localeCompare(b.month)); + return c.json(result); +}); + +app.get("/api/stats/daily", (c) => { + const { workspace } = getAgentFromQuery(c); + const memoryDir = path.join(workspace, "memory"); + const results: { date: string; count: number; size: number }[] = []; + let entries: fs.Dirent[]; + try { + entries = fs.readdirSync(memoryDir, { withFileTypes: true }); + } catch { + return c.json([]); + } + const dateMap = new Map(); + for (const entry of entries) { + if (!entry.isFile() || !entry.name.endsWith(".md")) continue; + const match = entry.name.match(/^(\d{4}-\d{2}-\d{2})/); + if (!match) continue; + const date = match[1]; + try { + const stat = fs.statSync(path.join(memoryDir, entry.name)); + const existing = dateMap.get(date); + if (existing) { + existing.count++; + existing.size += stat.size; + } else { + dateMap.set(date, { count: 1, size: stat.size }); + } + } catch { /* skip */ } + } + for (const [date, val] of dateMap) { + results.push({ date, count: val.count, size: val.size }); + } + results.sort((a, b) => a.date.localeCompare(b.date)); + return c.json(results); +}); + +app.get("/api/info", (c) => { + const { workspace } = getAgentFromQuery(c); + let name = "Unknown Bot"; + let description = ""; + for (const fname of ["IDENTITY.md", "SOUL.md"]) { + const fpath = path.join(workspace, fname); + if (fs.existsSync(fpath)) { + const content = fs.readFileSync(fpath, "utf-8"); + const heading = content.match(/^#\s+(.+)/m); + if (heading) name = heading[1].trim(); + const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#")); + if (lines.length > 0) description = lines[0].trim().substring(0, 200); + break; + } + } + return c.json({ name, version: "1.0.0", description }); +}); + +app.get("/api/system", (c) => { + const { workspace } = getAgentFromQuery(c); + const uptime = os.uptime(); + const memTotal = os.totalmem(); + const memFree = os.freemem(); + const load = os.loadavg(); + const platform = `${os.platform()} ${os.release()}`; + const hostname = os.hostname(); + + const today = new Date().toISOString().split("T")[0]; + const todayPath = path.join(workspace, "memory", `${today}.md`); + let todayMemory = null; + if (fs.existsSync(todayPath)) { + const content = fs.readFileSync(todayPath, "utf-8"); + todayMemory = { + filename: `memory/${today}.md`, + snippet: content.split("\n").slice(0, 10).join("\n"), + length: content.length, + }; + } + + const totalFiles = collectMdFiles(workspace).length; + + return c.json({ + uptime, memTotal, memFree, memUsed: memTotal - memFree, + load, platform, hostname, todayMemory, totalFiles, + }); +}); + +app.get("/api/agent/status", async (c) => { + // 1. Config + const home = os.homedir(); + const configPath = path.join(home, ".openclaw", "openclaw.json"); + let safeConfig: any = {}; + try { + if (fs.existsSync(configPath)) { + const raw = JSON.parse(fs.readFileSync(configPath, "utf-8")); + // Whitelist specific fields + safeConfig = { + version: raw.version, + update: raw.update, + models: { mode: raw.models?.mode }, + agents: { defaults: raw.agents?.defaults }, + gateway: { + port: raw.gateway?.port, + mode: raw.gateway?.mode, + }, + }; + } + } catch (e) { + console.error("Failed to read config", e); + safeConfig = { error: "Could not read config" }; + } + + // 2. Gateway Status + let gatewayStatus = null; + try { + const { stdout } = await exec("openclaw gateway status --json"); + gatewayStatus = JSON.parse(stdout); + } catch (e) { + // console.error("Failed to get gateway status", e); + // fallback or null + } + + // 3. Heartbeat + let heartbeat = null; + try { + const hbPath = path.join(DEFAULT_WORKSPACE, "memory", "heartbeat-state.json"); + if (fs.existsSync(hbPath)) { + heartbeat = JSON.parse(fs.readFileSync(hbPath, "utf-8")); + } + } catch (e) { + console.error("Failed to read heartbeat", e); + } + + return c.json({ + config: safeConfig, + gateway: gatewayStatus, + heartbeat + }); +}); + +// --------------------------------------------------------------------------- +// Gateway Chat Proxy +// --------------------------------------------------------------------------- +app.post("/api/gateway/chat", async (c) => { + const { gatewayUrl, token, messages } = await c.req.json(); + if (!gatewayUrl || !token || !messages) { + return c.json({ error: "Missing gatewayUrl, token, or messages" }, 400); + } + try { + const url = `${gatewayUrl.replace(/\/+$/, "")}/v1/chat/completions`; + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 30000); + + const resp = await fetch(url, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ model: "default", messages }), + signal: controller.signal, + }); + clearTimeout(timeout); + + if (!resp.ok) { + const text = await resp.text(); + return c.json({ error: text }, resp.status as any); + } + const data = await resp.json(); + return c.json(data); + } catch (err: any) { + if (err.name === "AbortError") { + return c.json({ error: "Gateway request timeout (30s)" }, 504); + } + return c.json({ error: err.message || "Gateway request failed" }, 502); + } +}); + +// --------------------------------------------------------------------------- +// AI Summarize +// --------------------------------------------------------------------------- +const GATEWAY_CHAT_URL = process.env.GATEWAY_CHAT_URL || "http://silicon-01:3001/v1/chat/completions"; +const SUMMARIZE_MODEL = process.env.SUMMARIZE_MODEL || "kimi-k2.5"; + +app.post("/api/summarize", async (c) => { + const { workspace } = getAgentFromQuery(c); + const { path: filePath, content: providedContent, save } = await c.req.json(); + const full = safePath(filePath, workspace); + if (!full) return c.json({ error: "Invalid path" }, 400); + + let content = providedContent; + if (!content) { + if (!fs.existsSync(full)) return c.json({ error: "Not found" }, 404); + content = fs.readFileSync(full, "utf-8"); + } + + // Strip existing frontmatter for summarization + const bodyMatch = content.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?([\s\S]*)$/); + const body = bodyMatch ? bodyMatch[1] : content; + + if (body.trim().length < 50) { + return c.json({ error: "Content too short to summarize" }, 400); + } + + try { + const resp = await fetch(GATEWAY_CHAT_URL, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + model: SUMMARIZE_MODEL, + messages: [ + { + role: "system", + content: "You are a concise summarizer. Given a markdown document, produce a brief summary (1-3 sentences, max 200 chars). Reply with ONLY the summary text, no quotes, no prefix.", + }, + { role: "user", content: body.slice(0, 8000) }, + ], + max_tokens: 256, + temperature: 0.3, + }), + signal: AbortSignal.timeout(30000), + }); + + if (!resp.ok) { + const text = await resp.text(); + return c.json({ error: `Gateway error: ${text}` }, 502); + } + + const data: any = await resp.json(); + const summary = (data.choices?.[0]?.message?.content || "").trim(); + if (!summary) return c.json({ error: "Empty summary returned" }, 502); + + // Optionally save to file frontmatter + if (save) { + const existingFm = content.match(/^---\r?\n([\s\S]*?)\r?\n---/); + let newContent: string; + if (existingFm) { + // Update or add summary field in existing frontmatter + const fmContent = existingFm[1]; + if (/^summary:/m.test(fmContent)) { + const updatedFm = fmContent.replace(/^summary:.*$/m, `summary: "${summary.replace(/"/g, '\\"')}"`); + newContent = content.replace(existingFm[0], `---\n${updatedFm}\n---`); + } else { + newContent = content.replace(existingFm[0], `---\n${fmContent}\nsummary: "${summary.replace(/"/g, '\\"')}"\n---`); + } + } else { + newContent = `---\nsummary: "${summary.replace(/"/g, '\\"')}"\n---\n${content}`; + } + fs.writeFileSync(full, newContent, "utf-8"); + const stat = fs.statSync(full); + return c.json({ summary, saved: true, mtime: stat.mtime }); + } + + return c.json({ summary, saved: false }); + } catch (err: any) { + if (err.name === "AbortError" || err.name === "TimeoutError") { + return c.json({ error: "Gateway timeout (30s)" }, 504); + } + return c.json({ error: err.message || "Summarize failed" }, 502); + } +}); + +// --------------------------------------------------------------------------- +// WebSocket — live file change notifications +// --------------------------------------------------------------------------- +const wsClients = new Set(); + +app.get("/ws", upgradeWebSocket(() => ({ + onOpen(_event, ws) { + wsClients.add(ws); + }, + onClose(_event, ws) { + wsClients.delete(ws); + }, +}))); + +function broadcast(data: object) { + const msg = JSON.stringify(data); + for (const ws of wsClients) { + try { ws.send(msg); } catch { /* ignore */ } + } +} + +const watcher = watch(path.join(DEFAULT_WORKSPACE, "**/*.md"), { + ignoreInitial: true, + ignored: /(^|[/\\])\.(git|node_modules)/, + awaitWriteFinish: { stabilityThreshold: 300 }, +}); + +watcher.on("all", (event, filePath) => { + const rel = path.relative(DEFAULT_WORKSPACE, filePath); + broadcast({ type: "file-change", event, path: rel }); +}); + +// --------------------------------------------------------------------------- +// Workspace assets (images, SVGs, etc.) +// --------------------------------------------------------------------------- +app.get("/workspace-assets/*", async (c) => { + const { workspace } = getAgentFromQuery(c); + const assetPath = c.req.path.replace("/workspace-assets/", ""); + const fullPath = path.join(workspace, "assets", assetPath); + if (!fullPath.startsWith(path.join(workspace, "assets"))) { + return c.json({ error: "Invalid path" }, 403); + } + if (!fs.existsSync(fullPath)) { + return c.json({ error: "Not found" }, 404); + } + const ext = path.extname(fullPath).toLowerCase(); + const mimeTypes: Record = { + ".svg": "image/svg+xml", + ".png": "image/png", + ".jpg": "image/jpeg", + ".jpeg": "image/jpeg", + ".gif": "image/gif", + ".webp": "image/webp", + }; + const contentType = mimeTypes[ext] || "application/octet-stream"; + const content = fs.readFileSync(fullPath); + c.header("Content-Type", contentType); + c.header("Cache-Control", "public, max-age=3600"); + return c.body(content); +}); + +// --------------------------------------------------------------------------- +// Cron API — read OpenClaw cron jobs and run history +// --------------------------------------------------------------------------- +const CRON_JOBS_FILE = path.join(os.homedir(), ".openclaw", "cron", "jobs.json"); +const CRON_RUNS_DIR = path.join(os.homedir(), ".openclaw", "cron", "runs"); + +interface CronJob { + id: string; + name?: string; + enabled: boolean; + schedule?: { kind: string; expr?: string; everyMs?: number; at?: string }; + payload?: { kind: string; text?: string; message?: string }; + sessionTarget?: string; + agentId?: string; + wakeMode?: string; + state?: { nextRunAtMs?: number; lastRunAtMs?: number; lastStatus?: string }; + delivery?: { mode?: string }; +} + +function readCronJobs(): CronJob[] { + try { + const data = fs.readFileSync(CRON_JOBS_FILE, "utf-8"); + const json = JSON.parse(data); + return json.jobs || []; + } catch { + return []; + } +} + +function writeCronJobs(jobs: CronJob[]): boolean { + try { + fs.copyFileSync(CRON_JOBS_FILE, CRON_JOBS_FILE + ".bak"); + fs.writeFileSync(CRON_JOBS_FILE, JSON.stringify({ version: 1, jobs }, null, 2)); + return true; + } catch { + return false; + } +} + +function readCronRuns(jobId: string): any[] { + try { + const runFile = path.join(CRON_RUNS_DIR, `${jobId}.jsonl`); + const data = fs.readFileSync(runFile, "utf-8"); + return data.trim().split("\n").filter(Boolean).map(line => { + try { return JSON.parse(line); } catch { return null; } + }).filter(Boolean).reverse(); + } catch { + return []; + } +} + +function formatCronJob(job: CronJob) { + let scheduleDisplay = "-"; + if (job.schedule) { + if (job.schedule.kind === "cron" && job.schedule.expr) scheduleDisplay = job.schedule.expr; + else if (job.schedule.kind === "every" && job.schedule.everyMs) scheduleDisplay = `every ${Math.round(job.schedule.everyMs / 60000)}m`; + else if (job.schedule.kind === "at" && job.schedule.at) scheduleDisplay = `at ${job.schedule.at}`; + } + return { + id: job.id, + name: job.name || "Unnamed", + enabled: job.enabled !== false, + schedule: scheduleDisplay, + scheduleRaw: job.schedule, + nextRun: job.state?.nextRunAtMs ? new Date(job.state.nextRunAtMs).toISOString() : null, + lastRun: job.state?.lastRunAtMs ? new Date(job.state.lastRunAtMs).toISOString() : null, + lastStatus: job.state?.lastStatus || null, + sessionTarget: job.sessionTarget || job.agentId || "-", + wakeMode: job.wakeMode || "next-heartbeat", + payloadKind: job.payload?.kind || "-", + deliveryMode: job.delivery?.mode || "-", + }; +} + +app.get("/api/crons", (c) => { + const jobs = readCronJobs(); + return c.json({ crons: jobs.map(formatCronJob) }); +}); + +app.get("/api/crons/:id/runs", (c) => { + const { id } = c.req.param(); + const runs = readCronRuns(id); + return c.json({ runs: runs.slice(0, 30) }); +}); + +app.post("/api/crons/:id/toggle", async (c) => { + const { id } = c.req.param(); + const { enabled } = await c.req.json(); + try { + const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json"); + const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); + const port = config.gateway?.port || 18789; + const token = config.gateway?.auth?.token || ""; + + const method = enabled ? "cron.update" : "cron.update"; + const resp = await fetch(`http://127.0.0.1:${port}/rpc`, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(token ? { Authorization: `Bearer ${token}` } : {}), + }, + body: JSON.stringify({ + jsonrpc: "2.0", + id: 1, + method: "cron.update", + params: { jobId: id, patch: { enabled } }, + }), + signal: AbortSignal.timeout(10000), + }); + const result = await resp.json() as any; + if (result.error) { + return c.json({ success: false, error: result.error.message }, 500); + } + return c.json({ success: true, job: result.result }); + } catch (e: any) { + // Fallback to direct file write + const jobs = readCronJobs(); + const idx = jobs.findIndex(j => j.id === id); + if (idx === -1) return c.json({ error: "Job not found" }, 404); + jobs[idx].enabled = enabled; + const ok = writeCronJobs(jobs); + return c.json({ success: ok, job: formatCronJob(jobs[idx]) }); + } +}); + +app.post("/api/crons/:id/run", async (c) => { + const { id } = c.req.param(); + try { + const { execSync } = await import("child_process"); + const scriptPath = path.join(import.meta.dirname || __dirname, "cron-trigger.mjs"); + const result = execSync(`node ${scriptPath} ${id}`, { + timeout: 12000, + cwd: path.dirname(scriptPath), + encoding: "utf-8", + }).trim(); + const parsed = JSON.parse(result) as any; + return c.json(parsed, parsed.success ? 200 : 500); + } catch (e: any) { + const stderr = e.stderr?.trim() || e.message; + return c.json({ success: false, error: stderr }, 500); + } +}); + +// System Crons API — heartbeat, compaction, pruning, session cleanup +// --------------------------------------------------------------------------- +app.get("/api/system-crons", (c) => { + try { + const configPath = path.join(os.homedir(), ".openclaw", "openclaw.json"); + const config = JSON.parse(fs.readFileSync(configPath, "utf-8")); + const defaults = config.agents?.defaults || {}; + const agentList = config.agents?.list || []; + + const systemCrons: any[] = []; + + // Heartbeat + const hbEvery = defaults.heartbeat?.every || "disabled"; + systemCrons.push({ + id: "sys-heartbeat", + name: "💓 心跳检测", + type: "heartbeat", + schedule: hbEvery === "disabled" ? "禁用" : `每 ${hbEvery}`, + enabled: hbEvery !== "disabled", + description: "定期唤醒 agent 执行 HEARTBEAT.md 检查", + agents: agentList.map((a: any) => ({ + id: a.id, + name: a.identity?.name || a.name || a.id, + heartbeat: a.heartbeat?.every || hbEvery, + enabled: (a.heartbeat?.every || hbEvery) !== "disabled", + })), + }); + + // Compaction + const compMode = defaults.compaction?.mode || "off"; + const flushEnabled = defaults.compaction?.memoryFlush?.enabled || false; + const flushThreshold = defaults.compaction?.memoryFlush?.softThresholdTokens; + systemCrons.push({ + id: "sys-compaction", + name: "🗜️ 上下文压缩", + type: "compaction", + schedule: "按需触发", + enabled: compMode !== "off", + description: `模式: ${compMode}${flushEnabled ? ` | Memory flush: ${flushThreshold ? flushThreshold + " tokens" : "启用"}` : ""}`, + }); + + // Context Pruning + const pruneMode = defaults.contextPruning?.mode || "off"; + const pruneTTL = defaults.contextPruning?.ttl; + systemCrons.push({ + id: "sys-context-pruning", + name: "✂️ 上下文修剪", + type: "context-pruning", + schedule: pruneTTL ? `TTL ${pruneTTL}` : "按需", + enabled: pruneMode !== "off", + description: `模式: ${pruneMode}${pruneTTL ? ` | 缓存过期: ${pruneTTL}` : ""}`, + }); + + // Session cleanup (2026.2.9 feature) + systemCrons.push({ + id: "sys-session-cleanup", + name: "🗑️ Session 清理", + type: "session-cleanup", + schedule: "自动", + enabled: true, + description: "自动修剪过期 session,防止磁盘占满", + }); + + // QMD Memory refresh + const qmd = config.memory?.qmd; + if (qmd && config.memory?.backend === "qmd") { + systemCrons.push({ + id: "sys-qmd-refresh", + name: "🧠 QMD 记忆索引", + type: "qmd", + schedule: qmd.update?.interval ? `每 ${qmd.update.interval}` : "手动", + enabled: true, + description: `后台刷新: ${qmd.update?.onBoot ? "启动时 + " : ""}${qmd.update?.interval || "手动"}`, + }); + } + + return c.json({ systemCrons }); + } catch (e: any) { + return c.json({ error: e.message }, 500); + } +}); + +// --------------------------------------------------------------------------- +// Static file serving +// --------------------------------------------------------------------------- +if (fs.existsSync(STATIC_DIR)) { + app.use("/assets/*", serveStatic({ + root: STATIC_DIR, + rewriteRequestPath: (p) => p, + })); + app.use("*", serveStatic({ root: STATIC_DIR })); + // SPA fallback + app.get("*", (c) => { + const html = fs.readFileSync(path.join(STATIC_DIR, "index.html"), "utf-8"); + c.header("Cache-Control", "no-cache, no-store, must-revalidate"); + return c.html(html); + }); +} + +// --------------------------------------------------------------------------- +// Start +// --------------------------------------------------------------------------- +if (process.env.NODE_ENV !== 'test') { + const server = serve({ fetch: app.fetch, port: PORT, hostname: "0.0.0.0" }, (info) => { + console.log(`📝 Memory Viewer running at http://localhost:${info.port}`); + console.log(`📂 Default Workspace: ${DEFAULT_WORKSPACE}`); + }); + injectWebSocket(server); +} diff --git a/memory-viewer/src/App.tsx b/memory-viewer/src/App.tsx new file mode 100644 index 0000000..76d0c52 --- /dev/null +++ b/memory-viewer/src/App.tsx @@ -0,0 +1,624 @@ +import { useEffect, useState, useCallback } from "react"; +import { fetchFiles, fetchSkills, setBaseUrl, getBaseUrl, type FileNode, type SkillInfo } from "./api"; +import { FileTree } from "./components/FileTree"; +import { FileViewer } from "./components/FileViewer"; +import { Dashboard } from "./components/Dashboard"; +import { SearchPanel } from "./components/SearchPanel"; +import { Connections } from "./components/Connections"; +import { Changelog } from "./components/Changelog"; +import { SkillsPage } from "./components/SkillsPage"; +import { Timeline } from "./components/Timeline"; +import { useWebSocket } from "./hooks/useWebSocket"; +import { useTheme } from "./hooks/useTheme"; +import { useSensitiveState, SensitiveProvider } from "./hooks/useSensitive"; +import { useConnections } from "./hooks/useConnections"; +import { useAgents } from "./hooks/useAgents"; +import { AgentStatusPage } from "./components/AgentStatus"; +import { SettingsPage } from "./components/SettingsPage"; +import { Tags } from "./components/Tags"; +import { CronManager } from "./components/CronManager"; +import { BookOpen, X, List, MagnifyingGlass, Sun, Moon, Eye, EyeSlash, Translate, ShareNetwork, CaretDown, CaretUp, ArrowsClockwise, Gear, Monitor, PuzzlePiece, CaretRight, Calendar, SquaresFour, Power, Clock, Tag, Robot, Timer } from "@phosphor-icons/react"; +import { useSyncExternalStore } from "react"; +import { pluginRegistry } from "./plugins/registry"; +import { useZoom } from "./hooks/useZoom"; +import { useMarkdownTheme } from "./themes"; +import { useResizableSidebar } from "./hooks/useResizableSidebar"; +import { useLocaleState, LocaleContext } from "./hooks/useLocale"; + +export default function App() { + const [files, setFiles] = useState([]); + const [skills, setSkills] = useState([]); + const [activeFile, setActiveFile] = useState(""); + const [view, setView] = useState<"dashboard" | "file" | "connections" | "changelog" | "agent-status" | "skills" | "timeline" | "tags" | "cron" | "settings">("dashboard"); + const [sidebarOpen, setSidebarOpen] = useState(false); + const [searchOpen, setSearchOpen] = useState(false); + const [refreshKey, setRefreshKey] = useState(0); + const [botSelectorOpen, setBotSelectorOpen] = useState(false); + const [agentSelectorOpen, setAgentSelectorOpen] = useState(false); + const [settingsOpen, setSettingsOpen] = useState(false); + const [quickAccessOpen, setQuickAccessOpen] = useState(() => localStorage.getItem("memory-viewer-quickaccess-open") === "true"); + const [teslaMode, setTeslaMode] = useState(() => localStorage.getItem("memory-viewer-tesla") === "true"); + const { zoom, setZoom, ZOOM_LEVELS } = useZoom(); + const { current: currentMdTheme, setTheme: setMdTheme, themes: mdThemes } = useMarkdownTheme(); + const { width: sidebarWidth, onMouseDown: onResizeMouseDown, onTouchStart: onResizeTouchStart } = useResizableSidebar(); + const { theme, toggle: toggleTheme } = useTheme(); + const sensitive = useSensitiveState(); + const localeState = useLocaleState(); + const { t, toggleLocale, locale } = localeState; + const connState = useConnections(); + const agentsState = useAgents(); + const pluginVersion = useSyncExternalStore(pluginRegistry.subscribe, pluginRegistry.getSnapshot); + const allPlugins = pluginRegistry.getAll(); + + // Sync baseUrl when active connection changes + useEffect(() => { + setBaseUrl(connState.active.url); + }, [connState.active]); + + const loadFiles = useCallback(() => { + fetchFiles().then(setFiles).catch(console.error); + fetchSkills().then(setSkills).catch(console.error); + }, []); + + // Reload files when active connection or agent changes + useEffect(() => { + loadFiles(); + }, [loadFiles, connState.active.id, agentsState.selectedAgentId]); + + // Live reload via WebSocket + useWebSocket((data) => { + if (data.type === "file-change") { + loadFiles(); + if (data.path === activeFile) { + setRefreshKey((k) => k + 1); + } + } + }, connState.active.url); + + // Ctrl+K to open search + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if ((e.ctrlKey || e.metaKey) && e.key === "k") { + e.preventDefault(); + setSearchOpen(true); + } + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, []); + + // Close bot selector on outside click + useEffect(() => { + if (!botSelectorOpen) return; + const handler = () => setBotSelectorOpen(false); + setTimeout(() => document.addEventListener("click", handler), 0); + return () => document.removeEventListener("click", handler); + }, [botSelectorOpen]); + + // Close agent selector on outside click + useEffect(() => { + if (!agentSelectorOpen) return; + const handler = () => setAgentSelectorOpen(false); + setTimeout(() => document.addEventListener("click", handler), 0); + return () => document.removeEventListener("click", handler); + }, [agentSelectorOpen]); + + // Close settings on outside click + useEffect(() => { + if (!settingsOpen) return; + const handler = () => setSettingsOpen(false); + setTimeout(() => document.addEventListener("click", handler), 0); + return () => document.removeEventListener("click", handler); + }, [settingsOpen]); + + // Sync hash → state on load and popstate + useEffect(() => { + const readHash = () => { + const hash = window.location.hash; + if (hash.startsWith("#/file/")) { + const path = decodeURIComponent(hash.slice(7)); + if (path) { + setActiveFile(path); + setView("file"); + return; + } + } + if (hash === "#/agent-status") { setView("agent-status"); return; } + if (hash === "#/connections") { setView("connections"); return; } + if (hash === "#/changelog") { setView("changelog"); return; } + if (hash === "#/skills") { setView("skills"); return; } + if (hash === "#/timeline") { setView("timeline"); return; } + if (hash === "#/tags") { setView("tags"); return; } + if (hash === "#/cron") { setView("cron"); return; } + if (hash === "#/settings") { setView("settings"); return; } + }; + readHash(); + window.addEventListener("popstate", readHash); + return () => window.removeEventListener("popstate", readHash); + }, []); + + const openFile = (path: string) => { + setActiveFile(path); + setView("file"); + setSidebarOpen(false); + window.history.pushState(null, "", `#/file/${encodeURIComponent(path)}`); + }; + + const goHome = () => { + setView("dashboard"); + setActiveFile(""); + setSidebarOpen(false); + window.history.pushState(null, "", window.location.pathname); + }; + + const switchBot = (id: string) => { + connState.switchTo(id); + setBotSelectorOpen(false); + setView("dashboard"); + setActiveFile(""); + }; + + const switchAgent = (agentId: string) => { + agentsState.selectAgent(agentId); + setAgentSelectorOpen(false); + setView("dashboard"); + setActiveFile(""); + // Reload files for new agent + setTimeout(() => loadFiles(), 0); + }; + + const online = connState.statuses[connState.active.id] ?? (connState.active.isLocal ? true : false); + const todayFile = `memory/${new Date().toISOString().slice(0, 10)}.md`; + + // Show agent selector only for local connections with multiple agents + const showAgentSelector = connState.active.isLocal && agentsState.agents.length > 1; + + return ( + + +
+ {/* Mobile overlay */} + {sidebarOpen && ( +
setSidebarOpen(false)} /> + )} + + {/* Sidebar - Redesigned */} + + + {/* Main content */} +
+ {/* Mobile top bar */} +
+ + + {view === "file" ? activeFile : view === "changelog" ? t("changelog.title") : view === "connections" ? t("connections.title") : view === "agent-status" ? t("sidebar.agentConfig") : view === "timeline" ? t("timeline.title") : view === "tags" ? t("tags.title") : view === "cron" ? t("cron.title") : view === "settings" ? t("settings.title") : t("dashboard.title")} + + + + + +
+ +
+ {view === "changelog" ? ( + + ) : view === "agent-status" ? ( + + ) : view === "skills" ? ( + + ) : view === "connections" ? ( +
+ +
+ ) : view === "timeline" ? ( +
+ +
+ ) : view === "tags" ? ( +
+ +
+ ) : view === "cron" ? ( + + ) : view === "settings" ? ( + + ) : view === "dashboard" ? ( +
+ +
+ ) : ( + + )} +
+
+ + {/* Search modal */} + {searchOpen && ( + setSearchOpen(false)} /> + )} +
+ + + ); +} diff --git a/memory-viewer/src/api.ts b/memory-viewer/src/api.ts new file mode 100644 index 0000000..31b5143 --- /dev/null +++ b/memory-viewer/src/api.ts @@ -0,0 +1,404 @@ +/** API client for Memory Viewer backend. */ + +let _baseUrl = ""; +let _currentAgent: string | null = null; + +export function getBaseUrl(): string { + return _baseUrl; +} + +export function setBaseUrl(url: string) { + _baseUrl = url.replace(/\/+$/, ""); +} + +export function getCurrentAgent(): string | null { + return _currentAgent; +} + +export function setCurrentAgent(agentId: string | null) { + _currentAgent = agentId; +} + +// Helper to build URL with agent parameter +function buildUrl(endpoint: string, params?: Record): string { + const base = _baseUrl || window.location.origin; + const url = new URL(`${base}${endpoint}`); + if (_currentAgent) { + url.searchParams.set("agent", _currentAgent); + } + if (params) { + Object.entries(params).forEach(([key, value]) => { + url.searchParams.set(key, value); + }); + } + return url.toString(); +} + +export interface FileNode { + name: string; + type: "file" | "dir"; + path: string; + children?: FileNode[]; +} + +export interface FileData { + content: string; + mtime: string; + size: number; +} + +export interface SystemInfo { + uptime: number; + memTotal: number; + memFree: number; + memUsed: number; + load: number[]; + platform: string; + hostname: string; + totalFiles: number; + todayMemory: { + filename: string; + snippet: string; + length: number; + } | null; +} + +export interface SearchResult { + path: string; + matches: { line: number; text: string }[]; +} + +export interface BotInfo { + name: string; + version: string; + description: string; +} + +export interface SkillInfo { id: string; name: string; description: string; path: string; } + +// Agent types +export interface AgentInfo { + id: string; + name: string; + workspace: string; + emoji: string; + skills?: string[]; +} + +export async function fetchAgents(): Promise { + const r = await fetch(`${_baseUrl}/api/agents`); + if (!r.ok) throw new Error("Failed to load agents"); + return r.json(); +} + +export async function fetchSkills(): Promise { + const r = await fetch(buildUrl("/api/skills")); + return r.json(); +} + +export async function fetchFiles(): Promise { + const r = await fetch(buildUrl("/api/files")); + return r.json(); +} + +export async function fetchFile(path: string): Promise { + const r = await fetch(buildUrl("/api/file", { path })); + if (!r.ok) throw new Error("Failed to load file"); + return r.json(); +} + +export interface SaveResult { + ok: boolean; + mtime: string; +} + +export interface ConflictResult { + error: "conflict"; + message: string; + serverMtime: string; + serverContent: string; +} + +export async function saveFile( + path: string, + content: string, + expectedMtime?: string +): Promise { + const r = await fetch(buildUrl("/api/file"), { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ path, content, expectedMtime }), + }); + return r.json(); +} + +export async function fetchSystem(): Promise { + const r = await fetch(buildUrl("/api/system")); + return r.json(); +} + +export async function searchFiles(query: string): Promise { + const r = await fetch(buildUrl("/api/search", { q: query })); + return r.json(); +} + +export interface SemanticResult { + path: string; + title: string; + snippet: string; + score: number; +} + +export async function semanticSearch(query: string, mode: "bm25" | "vector" = "bm25"): Promise { + const r = await fetch(buildUrl("/api/semantic-search", { q: query, mode })); + return r.json(); +} + +export interface RecentFile { + path: string; + mtime: number; + size: number; +} + +export interface MonthlyStats { + month: string; + count: number; +} + +export interface TimelineEntry { + date: string; + path: string; + title: string; + preview: string; + tags: string[]; + charCount: number; +} + +export async function fetchTimeline(): Promise { + const r = await fetch(buildUrl("/api/timeline")); + return r.json(); +} + +// ============================================================================ +// Tags API +// ============================================================================ + +export interface TagInfo { + name: string; + count: number; + files: string[]; +} + +export interface FileWithTags { + path: string; + title: string; + preview: string; + date: string; + tags: string[]; +} + +export async function fetchTags(): Promise { + const r = await fetch(buildUrl("/api/tags")); + return r.json(); +} + +export async function fetchFilesByTag(tag: string): Promise { + const r = await fetch(buildUrl(`/api/files-by-tag/${encodeURIComponent(tag)}`)); + return r.json(); +} + +export async function fetchRecent(limit = 10): Promise { + const r = await fetch(buildUrl("/api/recent", { limit: String(limit) })); + return r.json(); +} + +export async function fetchMonthlyStats(): Promise { + const r = await fetch(buildUrl("/api/stats/monthly")); + return r.json(); +} + +export interface DailyStats { date: string; count: number; size: number; } + +export async function fetchDailyStats(): Promise { + const r = await fetch(buildUrl("/api/stats/daily")); + return r.json(); +} + +export async function fetchBotInfo(baseUrl = ""): Promise { + const url = baseUrl ? baseUrl.replace(/\/+$/, "") : _baseUrl; + const r = await fetch(`${url}/api/info${_currentAgent ? `?agent=${_currentAgent}` : ""}`); + return r.json(); +} + +export async function checkConnection(baseUrl: string): Promise { + try { + const r = await fetch(`${baseUrl.replace(/\/+$/, "")}/api/system`, { signal: AbortSignal.timeout(5000) }); + return r.ok; + } catch { + return false; + } +} + +export interface AgentStatus { + config: any; + gateway: any; + heartbeat: any; +} + +export interface WikilinkResolution { + found: boolean; + path: string | null; +} + +export async function resolveWikilink(link: string): Promise { + const r = await fetch(buildUrl("/api/resolve-wikilink", { link })); + return r.json(); +} + +export interface SummarizeResult { + summary: string; + saved: boolean; + mtime?: string; + error?: string; +} + +export async function summarizeFile(path: string, save = false): Promise { + const r = await fetch(buildUrl("/api/summarize"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ path, save }), + }); + const data = await r.json(); + if (!r.ok) throw new Error(data.error || "Summarize failed"); + return data; +} + +export async function fetchAgentStatus(): Promise { + const r = await fetch(`${_baseUrl}/api/agent/status${_currentAgent ? `?agent=${_currentAgent}` : ""}`); + return r.json(); +} + +// ============================================================================ +// Cron API +// ============================================================================ + +export interface CronJob { + id: string; + name: string; + enabled: boolean; + schedule: string; + scheduleRaw: any; + nextRun: string | null; + lastRun: string | null; + lastStatus: string | null; + sessionTarget: string; + wakeMode: string; + payloadKind: string; + deliveryMode: string; +} + +export interface CronRun { + status: string; + startedAt?: string; + runAtMs?: number; + ts?: number; + completedAt?: string; + sessionKey?: string; + durationMs?: number; +} + +export interface SystemCron { + id: string; + name: string; + type: string; + schedule: string; + enabled: boolean; + description: string; + agents?: { id: string; name: string; heartbeat: string; enabled: boolean }[]; +} + +export async function fetchSystemCrons(): Promise { + const r = await fetch(buildUrl("/api/system-crons")); + const data = await r.json(); + return data.systemCrons || []; +} + +export async function fetchCronJobs(): Promise { + const r = await fetch(buildUrl("/api/crons")); + const data = await r.json(); + return data.crons || []; +} + +export async function fetchCronRuns(jobId: string): Promise { + const r = await fetch(buildUrl(`/api/crons/${jobId}/runs`)); + const data = await r.json(); + return data.runs || []; +} + +export async function toggleCronJob(jobId: string, enabled: boolean): Promise<{ success: boolean }> { + const r = await fetch(buildUrl(`/api/crons/${jobId}/toggle`), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ enabled }), + }); + return r.json(); +} + +export async function runCronJob(jobId: string): Promise<{ success: boolean; result?: string; error?: string }> { + const r = await fetch(buildUrl(`/api/crons/${jobId}/run`), { + method: "POST", + headers: { "Content-Type": "application/json" }, + }); + return r.json(); +} + +export interface EmbeddingSettings { + enabled: boolean; + apiUrl: string; + apiKey: string; + model: string; + apiKeySet?: boolean; +} + +export interface Settings { + embedding: EmbeddingSettings; +} + +export interface Capabilities { + qmd: boolean; + qmdBm25: boolean; + qmdVector: boolean; + embeddingApi: boolean; +} + +export async function fetchCapabilities(): Promise { + try { + const r = await fetch(buildUrl("/api/capabilities")); + return r.json(); + } catch { + return { qmd: false, qmdBm25: false, qmdVector: false, embeddingApi: false }; + } +} + +export async function fetchSettings(): Promise { + const r = await fetch(buildUrl("/api/settings")); + return r.json(); +} + +export async function saveSettings(settings: Partial): Promise<{ success: boolean }> { + const r = await fetch(buildUrl("/api/settings"), { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(settings), + }); + return r.json(); +} + +export async function testEmbeddingConnection(settings?: Partial): Promise<{ success: boolean; error?: string }> { + const r = await fetch(buildUrl("/api/settings/test-embedding"), { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(settings || {}), + }); + return r.json(); +} diff --git a/memory-viewer/src/components/AgentStatus.test.tsx b/memory-viewer/src/components/AgentStatus.test.tsx new file mode 100644 index 0000000..ccaba28 --- /dev/null +++ b/memory-viewer/src/components/AgentStatus.test.tsx @@ -0,0 +1,42 @@ +import { describe, it, expect } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import { AgentStatusPage } from '../components/AgentStatus'; +import { LocaleContext } from '../hooks/useLocale'; + +// Mock dependencies +vi.mock('../api', () => ({ + fetchAgentStatus: vi.fn().mockResolvedValue({ + config: { version: '1.2.0', update: { channel: 'stable' } }, + gateway: { runtime: { status: 'running', pid: 1234 } }, + heartbeat: { lastRun: Date.now() } + }) +})); + +// Mock shiki +vi.mock('shiki', () => ({ + createHighlighter: vi.fn().mockResolvedValue({ + codeToHtml: () => '
mock code
' + }) +})); + +describe('AgentStatusPage', () => { + it('renders loading state initially', () => { + // We can't easily test loading state with async useEffect, + // but we can test the happy path after wait + }); + + it('renders status after load', async () => { + const mockLocale = { t: (k: string) => k, toggleLocale: () => {}, locale: 'en' as const }; + + render( + + + + ); + + // Should eventually show the version + expect(await screen.findByText('v1.2.0')).toBeInTheDocument(); + // Should show gateway running status key + expect(screen.getByText('agent.running')).toBeInTheDocument(); + }); +}); diff --git a/memory-viewer/src/components/AgentStatus.tsx b/memory-viewer/src/components/AgentStatus.tsx new file mode 100644 index 0000000..91140fc --- /dev/null +++ b/memory-viewer/src/components/AgentStatus.tsx @@ -0,0 +1,236 @@ +import { useEffect, useState } from "react"; +import { fetchAgentStatus, type AgentStatus } from "../api"; +import { createHighlighter } from "shiki"; +import { Pulse, HardDrives, Shield, Cpu, Clock, CaretDown, CaretRight, CheckCircle, XCircle, Heartbeat, Lightning } from "@phosphor-icons/react"; +import { useLocale } from "../hooks/useLocale"; + +function StatusCard({ title, icon: Icon, children, className = "" }: any) { + return ( +
+
+
+ +
+

{title}

+
+ {children} +
+ ); +} + +function StatusRow({ label, value, sub }: any) { + return ( +
+ {label} +
+
{value}
+ {sub &&
{sub}
} +
+
+ ); +} + +function TimeAgo({ date }: { date: string | number }) { + if (!date) return -; + const d = new Date(date); + const now = new Date(); + const diff = Math.floor((now.getTime() - d.getTime()) / 1000); + + let text = ""; + if (diff < 60) text = `${diff}s ago`; + else if (diff < 3600) text = `${Math.floor(diff / 60)}m ago`; + else if (diff < 86400) text = `${Math.floor(diff / 3600)}h ago`; + else text = `${Math.floor(diff / 86400)}d ago`; + + return {text}; +} + +// Module-level cache so switching tabs doesn't re-fetch +let _cache: { data: AgentStatus; html: string } | null = null; + +export function AgentStatusPage() { + const [data, setData] = useState(_cache?.data ?? null); + const [loading, setLoading] = useState(!_cache); + const [html, setHtml] = useState(_cache?.html ?? ""); + const [configExpanded, setConfigExpanded] = useState(false); + const { t } = useLocale(); + + useEffect(() => { + if (_cache) return; // Already have data, skip fetch + fetchAgentStatus() + .then(d => { + setData(d); + setLoading(false); + // Highlight config with dual theme + createHighlighter({ + themes: ['github-dark', 'github-light'], + langs: ['json'] + }).then(highlighter => { + const code = JSON.stringify(d.config, null, 2); + const out = highlighter.codeToHtml(code, { + lang: 'json', + themes: { dark: 'github-dark', light: 'github-light' }, + defaultColor: false, + }); + setHtml(out); + _cache = { data: d, html: out }; + }); + }) + .catch(err => { + console.error(err); + setLoading(false); + }); + }, []); + + if (loading) { + return ( +
+
+ {/* Header skeleton */} +
+
+
+
+
+
+
+ + {/* Cards skeleton */} +
+ {[1, 2, 3].map((i) => ( +
+
+
+
+
+
+
+
+
+
+
+
+ ))} +
+ + {/* Config skeleton */} +
+
+
+ ); + } + + if (!data) { + return ( +
+ {t("agent.error")} +
+ ); + } + + const gw = data.gateway || {}; + // Support both flat (gw.runtime) and nested (gw.service.runtime) structures + const runtime = gw.runtime || gw.service?.runtime || {}; + const gwInfo = gw.gateway || {}; + const isGwRunning = runtime.status === "running" || runtime.state === "active" || (runtime.pid && runtime.pid > 0); + const hb = data.heartbeat || {}; + const checks = hb.checks || {}; + + return ( +
+
+ + {/* Header */} +
+

+ + {t("agent.title")} +

+
+ v{data.config.version || "0.0.0"} + {data.config.update?.channel || "stable"} {t("agent.channel")} +
+
+ +
+ + {/* Gateway Card */} + +
+
+ {isGwRunning ? t("agent.running") : t("agent.stopped")} + {isGwRunning && gw.gateway?.uptime && {t("agent.uptime")}} +
+
+ + + +
+ + + {/* Models Card */} + +
+
{t("agent.primary")}
+
+ {data.config.agents?.defaults?.model?.primary || "default"} +
+
+ {/* If we had more model stats they would go here */} +
+ + {t("agent.ready")} +
+
+ + {/* Heartbeat Card */} + +
+ {hb.lastRun ? ( + <> +
+ +
+
{t("agent.lastActivity")}
+ + ) : ( +
{t("agent.noHeartbeat")}
+ )} +
+
+ {Object.entries(checks).map(([k, v]) => ( + : "-"} /> + ))} +
+
+ +
+ + {/* Config Section */} +
+ + + {configExpanded && ( +
+
Loading...
" }} + /> + + )} + + + + + ); +} diff --git a/memory-viewer/src/components/BootstrapWizard.tsx b/memory-viewer/src/components/BootstrapWizard.tsx new file mode 100644 index 0000000..572708e --- /dev/null +++ b/memory-viewer/src/components/BootstrapWizard.tsx @@ -0,0 +1,209 @@ +import { useState, useEffect, useCallback } from "react"; +import { SpinnerGap, CheckCircle, XCircle, Rocket, ArrowRight } from "@phosphor-icons/react"; +import { useLocale } from "../hooks/useLocale"; + +type Step = "checking" | "confirm" | "installing" | "verifying" | "success" | "failed"; + +interface BootstrapWizardProps { + name: string; + gatewayUrl: string; + token: string; + mvPort: number; + onComplete: (mvUrl: string) => void; + onCancel: () => void; +} + +function extractHost(gatewayUrl: string): string { + try { + return new URL(gatewayUrl).hostname; + } catch { + return gatewayUrl.replace(/^https?:\/\//, "").split(":")[0].split("/")[0]; + } +} + +const INSTALL_PROMPT = `Please install and start the Memory Viewer server. Run these commands: + +1. Clone the repo: + cd ~ && git clone https://github.com/silicondawn/memory-viewer.git + +2. Install dependencies and build: + cd memory-viewer && npm install && npm run build + +3. Start the server (replace WORKSPACE_DIR with your actual workspace path): + PORT=8901 WORKSPACE_DIR=~/clawd node --import tsx/esm server/index.ts & + +4. Verify it's running: + curl -s http://localhost:8901/api/system + +Please run these commands and confirm when the server is running.`; + +export function BootstrapWizard({ name, gatewayUrl, token, mvPort, onComplete, onCancel }: BootstrapWizardProps) { + const { t } = useLocale(); + const [step, setStep] = useState("checking"); + const [botResponse, setBotResponse] = useState(""); + const [error, setError] = useState(""); + + const host = extractHost(gatewayUrl); + const mvUrl = `http://${host}:${mvPort}`; + + const checkConnection = useCallback(async (): Promise => { + try { + const r = await fetch(`${mvUrl}/api/system`, { signal: AbortSignal.timeout(5000) }); + return r.ok; + } catch { + return false; + } + }, [mvUrl]); + + // Step 1: initial check + useEffect(() => { + if (step !== "checking") return; + checkConnection().then((ok) => { + if (ok) { + onComplete(mvUrl); + setStep("success"); + } else { + setStep("confirm"); + } + }); + }, [step, checkConnection, mvUrl, onComplete]); + + const doInstall = async () => { + setStep("installing"); + setBotResponse(""); + setError(""); + try { + const resp = await fetch("/api/gateway/chat", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + gatewayUrl, + token, + messages: [{ role: "user", content: INSTALL_PROMPT.replace(/PORT=8901/g, `PORT=${mvPort}`) }], + }), + }); + if (!resp.ok) { + const data = await resp.json().catch(() => ({ error: resp.statusText })); + throw new Error(data.error || `HTTP ${resp.status}`); + } + const data = await resp.json(); + const content = data.choices?.[0]?.message?.content || JSON.stringify(data); + setBotResponse(content); + + // Step 4: verify + setStep("verifying"); + // Wait a bit then check + await new Promise((r) => setTimeout(r, 3000)); + const ok = await checkConnection(); + if (ok) { + onComplete(mvUrl); + setStep("success"); + } else { + // Retry a few times + for (let i = 0; i < 3; i++) { + await new Promise((r) => setTimeout(r, 5000)); + if (await checkConnection()) { + onComplete(mvUrl); + setStep("success"); + return; + } + } + setStep("failed"); + } + } catch (err: any) { + setError(err.message); + setStep("failed"); + } + }; + + const retryVerify = async () => { + setStep("verifying"); + await new Promise((r) => setTimeout(r, 2000)); + const ok = await checkConnection(); + if (ok) { + onComplete(mvUrl); + setStep("success"); + } else { + setStep("failed"); + } + }; + + const inputStyle = { background: "var(--bg-hover)", color: "var(--text-primary)", border: "1px solid var(--border)" }; + + return ( +
+

+ Bootstrap: {name} +

+
+ {mvUrl} +
+ + {step === "checking" && ( +
+ {t("bootstrap.checking")} +
+ )} + + {step === "confirm" && ( +
+

{t("bootstrap.notInstalled")}

+

{t("bootstrap.installPrompt")}

+
+ + +
+
+ )} + + {step === "installing" && ( +
+
+ {t("bootstrap.installing")} +
+
+ )} + + {step === "verifying" && ( +
+ {botResponse && ( +
+
{t("bootstrap.botResponse")}:
+ {botResponse} +
+ )} +
+ {t("bootstrap.verifying")} +
+
+ )} + + {step === "success" && ( +
+ {t("bootstrap.success")} +
+ )} + + {step === "failed" && ( +
+ {botResponse && ( +
+
{t("bootstrap.botResponse")}:
+ {botResponse} +
+ )} + {error && ( +
{t("bootstrap.error")}: {error}
+ )} +
{t("bootstrap.failed")}
+
+ + +
+
+ )} +
+ ); +} diff --git a/memory-viewer/src/components/Changelog.tsx b/memory-viewer/src/components/Changelog.tsx new file mode 100644 index 0000000..8ea61b3 --- /dev/null +++ b/memory-viewer/src/components/Changelog.tsx @@ -0,0 +1,33 @@ +import { useEffect, useState } from "react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { useLocale } from "../hooks/useLocale"; +import { ArrowLeft } from "@phosphor-icons/react"; + +// Bundled at build time via ?raw +import changelogRaw from "../../CHANGELOG.md?raw"; + +export function Changelog({ onBack }: { onBack: () => void }) { + const { t } = useLocale(); + + return ( +
+
+ + +
+ + {changelogRaw} + +
+
+
+ ); +} diff --git a/memory-viewer/src/components/Connections.tsx b/memory-viewer/src/components/Connections.tsx new file mode 100644 index 0000000..93583ae --- /dev/null +++ b/memory-viewer/src/components/Connections.tsx @@ -0,0 +1,275 @@ +import { useState } from "react"; +import { type BotConnection } from "../hooks/useConnections"; +import { ShareNetwork, Plus, Trash, PencilSimple, Check, X, ArrowsClockwise } from "@phosphor-icons/react"; +import { useLocale } from "../hooks/useLocale"; +import { BootstrapWizard } from "./BootstrapWizard"; + +interface ConnectionsProps { + connections: BotConnection[]; + statuses: Record; + activeId: string; + onAdd: (conn: Omit) => void; + onUpdate: (id: string, updates: Partial) => void; + onRemove: (id: string) => void; + onSwitch: (id: string) => void; + onRefresh: () => void; +} + +type ConnMode = "direct" | "gateway"; + +interface GatewayBootstrap { + name: string; + gatewayUrl: string; + token: string; + mvPort: number; +} + +export function Connections({ connections, statuses, activeId, onAdd, onUpdate, onRemove, onSwitch, onRefresh }: ConnectionsProps) { + const { t } = useLocale(); + const [showForm, setShowForm] = useState(false); + const [editId, setEditId] = useState(null); + const [mode, setMode] = useState("direct"); + const [form, setForm] = useState({ name: "", url: "", token: "" }); + const [gwForm, setGwForm] = useState({ name: "", gatewayUrl: "", token: "", mvPort: "8901" }); + const [bootstrap, setBootstrap] = useState(null); + + const startAdd = () => { + setForm({ name: "", url: "", token: "" }); + setGwForm({ name: "", gatewayUrl: "", token: "", mvPort: "8901" }); + setEditId(null); + setMode("direct"); + setShowForm(true); + }; + + const startEdit = (conn: BotConnection) => { + setForm({ name: conn.name, url: conn.url, token: conn.token || "" }); + setEditId(conn.id); + setMode("direct"); + setShowForm(true); + }; + + const save = () => { + if (mode === "direct") { + if (!form.name.trim() || !form.url.trim()) return; + if (editId) { + onUpdate(editId, { name: form.name.trim(), url: form.url.trim(), token: form.token.trim() || undefined }); + } else { + onAdd({ name: form.name.trim(), url: form.url.trim(), token: form.token.trim() || undefined }); + } + setShowForm(false); + setEditId(null); + setTimeout(onRefresh, 500); + } else { + // Gateway mode — start bootstrap + if (!gwForm.name.trim() || !gwForm.gatewayUrl.trim() || !gwForm.token.trim()) return; + setBootstrap({ + name: gwForm.name.trim(), + gatewayUrl: gwForm.gatewayUrl.trim(), + token: gwForm.token.trim(), + mvPort: parseInt(gwForm.mvPort) || 8901, + }); + setShowForm(false); + } + }; + + const handleBootstrapComplete = (mvUrl: string) => { + if (!bootstrap) return; + onAdd({ name: bootstrap.name, url: mvUrl }); + setTimeout(onRefresh, 500); + }; + + const inputStyle = { + background: "var(--bg-hover)", + color: "var(--text-primary)", + border: "1px solid var(--border)", + }; + + return ( +
+
+

+ {t("connections.title")} +

+
+ + +
+
+ + {/* Bootstrap Wizard */} + {bootstrap && ( + setBootstrap(null)} + /> + )} + + {/* Add/Edit form */} + {showForm && ( +
+
+

+ {editId ? t("connections.edit") : t("connections.addNew")} +

+ {!editId && ( +
+ + +
+ )} +
+ + {mode === "direct" ? ( +
+ setForm((f) => ({ ...f, name: e.target.value }))} + /> + setForm((f) => ({ ...f, url: e.target.value }))} + /> + setForm((f) => ({ ...f, token: e.target.value }))} + /> +
+ ) : ( +
+ setGwForm((f) => ({ ...f, name: e.target.value }))} + /> + setGwForm((f) => ({ ...f, gatewayUrl: e.target.value }))} + /> + setGwForm((f) => ({ ...f, token: e.target.value }))} + /> + setGwForm((f) => ({ ...f, mvPort: e.target.value }))} + /> +
+ )} + +
+ + +
+
+ )} + + {/* Connection list */} +
+ {connections.map((conn) => { + const online = statuses[conn.id] ?? false; + const isActive = conn.id === activeId; + return ( +
onSwitch(conn.id)} + > +
+
+ + + {conn.name} + +
+ {!conn.isLocal && ( +
+ + +
+ )} +
+
+ {conn.isLocal ? "localhost (current)" : conn.url} +
+ {isActive && ( +
+ ● {t("connections.active")} +
+ )} +
+ ); + })} +
+
+ ); +} diff --git a/memory-viewer/src/components/ContributionHeatmap.tsx b/memory-viewer/src/components/ContributionHeatmap.tsx new file mode 100644 index 0000000..56a44ab --- /dev/null +++ b/memory-viewer/src/components/ContributionHeatmap.tsx @@ -0,0 +1,227 @@ +import { useMemo, useState, useSyncExternalStore } from "react"; +import type { DailyStats } from "../api"; +import { useLocale } from "../hooks/useLocale"; + +const CELL = 11; +const GAP = 2; +const STEP = CELL + GAP; + +const COLORS_DARK = ["#1a1c2b", "#1b3a2a", "#1a5c35", "#26a641", "#39d353"]; +const COLORS_LIGHT = ["#f0e8de", "#d4c4a0", "#b49555", "#92400e", "#78350f"]; + +function useIsDark() { + return useSyncExternalStore( + (cb) => { + const obs = new MutationObserver(cb); + obs.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] }); + return () => obs.disconnect(); + }, + () => document.documentElement.classList.contains("dark"), + ); +} + +function getLevel(size: number, thresholds: number[]): number { + if (size === 0) return 0; + for (let i = thresholds.length - 1; i >= 0; i--) { + if (size >= thresholds[i]) return i + 1; + } + return 1; +} + +function formatSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + return `${(bytes / 1024).toFixed(1)} KB`; +} + +interface Props { + data: DailyStats[]; + onOpenFile: (path: string) => void; +} + +export function ContributionHeatmap({ data, onOpenFile }: Props) { + const { t } = useLocale(); + const [tooltip, setTooltip] = useState<{ x: number; y: number; text: string } | null>(null); + const isDark = useIsDark(); + + const colors = isDark ? COLORS_DARK : COLORS_LIGHT; + + const { weeks, monthLabels, thresholds } = useMemo(() => { + const map = new Map(); + for (const d of data) map.set(d.date, d.size); + + // Compute thresholds from non-zero sizes + const sizes = data.map((d) => d.size).filter((s) => s > 0).sort((a, b) => a - b); + let th = [1, 1000, 3000, 6000]; + if (sizes.length >= 4) { + const q = (i: number) => sizes[Math.floor((i * (sizes.length - 1)) / 4)]; + th = [1, q(1), q(2), q(3)]; + } + + // Build 52 weeks ending today + const today = new Date(); + const todayDay = today.getDay(); // 0=Sun + // End of this week (Saturday) or today + const endDate = new Date(today); + const startDate = new Date(today); + startDate.setDate(startDate.getDate() - (52 * 7) - todayDay); + + const weeks: { date: string; size: number; dow: number }[][] = []; + const monthLabels: { col: number; label: string }[] = []; + let currentWeek: { date: string; size: number; dow: number }[] = []; + let lastMonth = -1; + + const cursor = new Date(startDate); + cursor.setDate(cursor.getDate() + 1); // start from next day + + while (cursor <= endDate) { + const dow = cursor.getDay(); + const dateStr = cursor.toISOString().slice(0, 10); + const month = cursor.getMonth(); + + if (dow === 0 && currentWeek.length > 0) { + weeks.push(currentWeek); + currentWeek = []; + } + + if (month !== lastMonth) { + monthLabels.push({ col: weeks.length, label: t(`month.${String(month + 1).padStart(2, "0")}`) }); + lastMonth = month; + } + + currentWeek.push({ date: dateStr, size: map.get(dateStr) || 0, dow }); + cursor.setDate(cursor.getDate() + 1); + } + if (currentWeek.length > 0) weeks.push(currentWeek); + + return { weeks, monthLabels, thresholds: th }; + }, [data, t]); + + const totalSize = data.reduce((s, d) => s + d.size, 0); + const totalDays = data.filter((d) => d.size > 0).length; + const dayLabels = ["", "Mon", "", "Wed", "", "Fri", ""]; + const labelWidth = 28; + const svgW = labelWidth + weeks.length * STEP + 10; + const svgH = 7 * STEP + 28; // extra for month labels + + return ( +
+
+ {totalDays} {t("heatmap.activeDays")} + {totalSize < 1024 * 1024 ? `${(totalSize / 1024).toFixed(1)} KB` : `${(totalSize / 1024 / 1024).toFixed(2)} MB`} {t("heatmap.total")} + {totalDays > 0 && ~{formatSize(Math.round(totalSize / totalDays))}{t("heatmap.perDay")}} +
+ + {/* Month labels */} + {monthLabels.map((m, i) => ( + + {m.label} + + ))} + {/* Day labels */} + {dayLabels.map((label, i) => + label ? ( + + {label} + + ) : null + )} + {/* Cells */} + {weeks.map((week, wi) => + week.map((day) => { + const level = getLevel(day.size, thresholds); + const x = labelWidth + wi * STEP; + const y = 18 + day.dow * STEP; + return ( + 0 ? "pointer" : "default" }} + onMouseEnter={(e) => { + const rect = (e.target as SVGRectElement).getBoundingClientRect(); + setTooltip({ + x: rect.left + CELL / 2, + y: rect.top - 8, + text: `${day.date} · ${day.size > 0 ? formatSize(day.size) : t("heatmap.noData")}`, + }); + }} + onMouseLeave={() => setTooltip(null)} + onClick={() => { + if (day.size > 0) onOpenFile(`memory/${day.date}.md`); + }} + /> + ); + }) + )} + + + {/* Legend */} +
+ {t("heatmap.less")} + {colors.map((c, i) => ( + + ))} + {t("heatmap.more")} +
+ + {/* Tooltip */} + {tooltip && ( +
+ {tooltip.text} +
+ )} +
+ ); +} diff --git a/memory-viewer/src/components/CronManager.tsx b/memory-viewer/src/components/CronManager.tsx new file mode 100644 index 0000000..4d212dc --- /dev/null +++ b/memory-viewer/src/components/CronManager.tsx @@ -0,0 +1,486 @@ +import { useEffect, useState } from "react"; +import { + Clock, + Play, + Pause, + Lightning, + CaretRight, + ArrowsClockwise, + CheckCircle, + XCircle, + Timer, + Heartbeat, + Gear, + X, +} from "@phosphor-icons/react"; +import { + fetchCronJobs, + fetchCronRuns, + fetchSystemCrons, + toggleCronJob, + runCronJob, + type CronJob, + type CronRun, + type SystemCron, +} from "../api"; +import { useLocale } from "../hooks/useLocale"; + +type Tab = "user" | "system"; + +export function CronManager() { + const { t } = useLocale(); + const [tab, setTab] = useState("user"); + const [jobs, setJobs] = useState([]); + const [systemCrons, setSystemCrons] = useState([]); + const [loading, setLoading] = useState(true); + const [selectedJob, setSelectedJob] = useState(null); + const [runs, setRuns] = useState([]); + const [runsLoading, setRunsLoading] = useState(false); + const [actionLoading, setActionLoading] = useState(null); + const [toast, setToast] = useState<{ message: string; type: "ok" | "error" } | null>(null); + + const showToast = (message: string, type: "ok" | "error" = "ok") => { + setToast({ message, type }); + setTimeout(() => setToast(null), 3000); + }; + + const loadJobs = async () => { + try { + const data = await fetchCronJobs(); + setJobs(data); + } catch (e) { + console.error("Failed to load cron jobs:", e); + } finally { + setLoading(false); + } + }; + + const loadSystemCrons = async () => { + try { + const data = await fetchSystemCrons(); + setSystemCrons(data); + } catch (e) { + console.error("Failed to load system crons:", e); + } + }; + + useEffect(() => { + loadJobs(); + loadSystemCrons(); + const interval = setInterval(loadJobs, 30000); + return () => clearInterval(interval); + }, []); + + useEffect(() => { + if (!selectedJob) { + setRuns([]); + return; + } + (async () => { + setRunsLoading(true); + try { + const data = await fetchCronRuns(selectedJob); + setRuns(data); + } catch (e) { + console.error("Failed to load runs:", e); + } finally { + setRunsLoading(false); + } + })(); + }, [selectedJob]); + + const handleToggle = async (job: CronJob) => { + setActionLoading(job.id); + try { + await toggleCronJob(job.id, !job.enabled); + showToast(`${!job.enabled ? "✅ 已启用" : "⏸ 已禁用"} ${job.name}`); + await loadJobs(); + } catch (e: any) { + showToast(`❌ ${e.message || "操作失败"}`, "error"); + } finally { + setActionLoading(null); + } + }; + + const handleRun = async (job: CronJob) => { + setActionLoading(`run-${job.id}`); + try { + const result = await runCronJob(job.id); + if (result.success) { + showToast(`⚡ ${job.name} 已触发运行`); + setTimeout(loadJobs, 2000); + } else { + showToast(`❌ ${result.error || "运行失败"}`, "error"); + } + } catch (e: any) { + showToast(`❌ ${e.message || "请求失败"}`, "error"); + } finally { + setActionLoading(null); + } + }; + + const enabledCount = jobs.filter((j) => j.enabled).length; + const disabledCount = jobs.length - enabledCount; + + function formatRelativeTime(iso: string | null): string { + if (!iso) return "-"; + const date = new Date(iso); + const now = new Date(); + const diffMs = date.getTime() - now.getTime(); + const absDiff = Math.abs(diffMs); + const isPast = diffMs < 0; + + if (absDiff < 60000) return isPast ? t("cron.justNow") : t("cron.soonLabel"); + if (absDiff < 3600000) { + const mins = Math.floor(absDiff / 60000); + return isPast ? `${mins}${t("cron.minAgo")}` : `${mins}${t("cron.minLater")}`; + } + if (absDiff < 86400000) { + const hours = Math.floor(absDiff / 3600000); + return isPast ? `${hours}${t("cron.hAgo")}` : `${hours}${t("cron.hLater")}`; + } + const days = Math.floor(absDiff / 86400000); + return isPast ? `${days}${t("cron.dAgo")}` : `${days}${t("cron.dLater")}`; + } + + function statusColor(status: string | null): string { + if (!status) return "var(--text-muted)"; + if (status === "ok" || status === "completed") return "#22c55e"; + if (status === "failed" || status === "error") return "#ef4444"; + return "#eab308"; + } + + if (loading) { + return ( +
+ + {t("cron.loading")} +
+ ); + } + + return ( +
+ {/* Toast */} + {toast && ( +
+ {toast.message} +
+ )} + +
+ {/* Header */} +
+
+

+ + {t("cron.title")} +

+

+ {t("cron.subtitle")} +

+
+ +
+ + {/* Tabs */} +
+ + +
+ + {tab === "user" && ( + <> + {/* Stats */} +
+
+
{jobs.length}
+
{t("cron.total")}
+
+
+
{enabledCount}
+
{t("cron.enabled")}
+
+
+
{disabledCount}
+
{t("cron.disabled")}
+
+
+ + {/* Job List */} +
+ {jobs.map((job) => ( +
+
+ +
+
+ + {job.name} + + + {job.schedule} + +
+
+ {job.lastRun && ( + + + {formatRelativeTime(job.lastRun)} + + )} + {job.nextRun && job.enabled && ( + + + {formatRelativeTime(job.nextRun)} + + )} + {job.sessionTarget} +
+
+
+ + + +
+
+ + {/* Run History */} + {selectedJob === job.id && ( +
+
+ {t("cron.runHistory")} +
+ {runsLoading ? ( +
+ + {t("cron.loading")} +
+ ) : runs.length === 0 ? ( +
+ {t("cron.noRuns")} +
+ ) : ( +
+ {runs.map((run, i) => ( +
+
+ {run.status === "completed" || run.status === "ok" ? ( + + ) : run.status === "failed" || run.status === "error" ? ( + + ) : ( + + )} + + {run.status || "unknown"} + +
+
+ {run.durationMs != null && {(run.durationMs / 1000).toFixed(1)}s} + + {(() => { + const ts = run.runAtMs || run.ts || run.startedAt; + if (!ts) return "—"; + const d = new Date(typeof ts === "number" ? ts : ts); + return isNaN(d.getTime()) ? "—" : d.toLocaleString(undefined, { + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + })()} + +
+
+ ))} +
+ )} +
+ )} +
+ ))} +
+ + {jobs.length === 0 && ( +
+ +

{t("cron.empty")}

+
+ )} + + )} + + {tab === "system" && ( +
+ {systemCrons.map((sc) => ( +
+
+ +
+
+ + {sc.name} + + + {sc.schedule} + +
+
+ {sc.description} +
+
+ + {sc.enabled ? "运行中" : "禁用"} + +
+ + {/* Heartbeat agent details */} + {sc.type === "heartbeat" && sc.agents && ( +
+
+ {sc.agents.map((agent) => ( +
+ + {agent.name} + ({agent.id}) + + + {agent.heartbeat === "disabled" ? "禁用" : agent.heartbeat} + +
+ ))} +
+
+ )} +
+ ))} + + {systemCrons.length === 0 && ( +
+ +

无法读取系统调度信息

+
+ )} +
+ )} +
+
+ ); +} diff --git a/memory-viewer/src/components/Dashboard.tsx b/memory-viewer/src/components/Dashboard.tsx new file mode 100644 index 0000000..48dbe5c --- /dev/null +++ b/memory-viewer/src/components/Dashboard.tsx @@ -0,0 +1,228 @@ +import { useEffect, useState } from "react"; +import { fetchSystem, fetchRecent, fetchDailyStats, fetchAgents, type SystemInfo, type RecentFile, type DailyStats, type AgentInfo } from "../api"; +import { ContributionHeatmap } from "./ContributionHeatmap"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import { SquaresFour, FileText, Clock, ChartBar, Lightning, Robot, Folder } from "@phosphor-icons/react"; +import { useLocale } from "../hooks/useLocale"; + +function formatUptime(seconds: number): string { + const d = Math.floor(seconds / 86400); + const h = Math.floor((seconds % 86400) / 3600); + const m = Math.floor((seconds % 3600) / 60); + const parts: string[] = []; + if (d > 0) parts.push(`${d}d`); + parts.push(`${h}h ${m}m`); + return parts.join(" "); +} + +function formatBytes(bytes: number): string { + return (bytes / 1024 / 1024 / 1024).toFixed(1) + " GB"; +} + +function timeAgo(mtime: number, t: (key: string) => string): string { + const diff = Date.now() - mtime; + const seconds = Math.floor(diff / 1000); + if (seconds < 60) return t("dashboard.justNow"); + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes} ${t("dashboard.minAgo")}`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}${t("dashboard.hAgo")}`; + const days = Math.floor(hours / 24); + return `${days}${t("dashboard.dAgo")}`; +} + +const PINNED_FILES = ["MEMORY.md", "SOUL.md", "USER.md", "AGENTS.md"]; + +export function Dashboard({ onOpenFile }: { onOpenFile: (path: string) => void }) { + const { t } = useLocale(); + const [info, setInfo] = useState(null); + const [recent, setRecent] = useState([]); + const [daily, setDaily] = useState([]); + const [agents, setAgents] = useState([]); + + useEffect(() => { + fetchSystem().then(setInfo).catch(console.error); + fetchRecent(10).then(setRecent).catch(console.error); + fetchDailyStats().then(setDaily).catch(console.error); + fetchAgents().then(setAgents).catch(console.error); + }, []); + + if (!info) { + return ( +
+
+ {t("dashboard.loading")} +
+ ); + } + + const memPercent = ((info.memUsed / info.memTotal) * 100).toFixed(1); + + // Quick access: pinned + recent files not in pinned + const recentQuick = recent + .map((r) => r.path) + .filter((p) => !PINNED_FILES.includes(p)) + .slice(0, 4); + + return ( +
+

+ {t("dashboard.title")} +

+ + {/* Stats */} +
+ + + l.toFixed(2)).join(" · ")} /> + +
+ + {/* Host info */} +
+ + {info.hostname} · {info.platform} +
+ + {/* Agents Overview */} + {agents.length > 1 && ( +
+

+ Agents + {agents.length} configured +

+
+ {agents.map((agent) => ( +
+ {agent.emoji} +
+
+ {agent.name} +
+
+ + {agent.workspace} +
+
+
+ ))} +
+
+ )} + + {/* Two-column: Recently Modified + Monthly Stats */} +
+ {/* Recently Modified */} +
+

+ {t("dashboard.recentlyModified")} +

+
+ {recent.slice(0, 5).map((f) => ( + + ))} + {recent.length === 0 && ( +

{t("dashboard.noFiles")}

+ )} +
+
+ + {/* Memory Activity Heatmap */} +
+

+ {t("dashboard.memoryByMonth")} +

+ {daily.length > 0 ? ( + + ) : ( +

{t("dashboard.noMemoryFiles")}

+ )} +
+
+ + {/* Today's memory */} +
+
+

+ {t("dashboard.todayMemory")} +

+ {info.todayMemory && ( + + )} +
+ {info.todayMemory ? ( +
+ + {info.todayMemory.snippet} + +
+ {info.todayMemory.length.toLocaleString()} {t("dashboard.characters")} +
+
+ ) : ( +

{t("dashboard.noMemoryToday")}

+ )} +
+ + {/* Quick Access */} +
+

+ {t("dashboard.quickAccess")} +

+
+ {PINNED_FILES.map((f) => ( + + ))} + {recentQuick.map((f) => ( + + ))} +
+
+
+ ); +} + +function StatCard({ label, value, sub }: { label: string; value: string; sub?: string }) { + return ( +
+
{label}
+
{value}
+ {sub &&
{sub}
} +
+ ); +} diff --git a/memory-viewer/src/components/FileTree.tsx b/memory-viewer/src/components/FileTree.tsx new file mode 100644 index 0000000..9f183c0 --- /dev/null +++ b/memory-viewer/src/components/FileTree.tsx @@ -0,0 +1,252 @@ +import { useState, useMemo, useEffect, useCallback } from "react"; +import type { FileNode } from "../api"; +import { CaretDown, CaretRight, Folder, FileText, Brain, Dna, Robot, User, Wrench, ListChecks, Heartbeat, IdentificationCard, Gear, Calendar, Clock, CaretUp } from "@phosphor-icons/react"; +import { useLocale } from "../hooks/useLocale"; + +/** Well-known bot config files shown in the top section */ +const BOT_FILES = new Set([ + "AGENTS.md", "SOUL.md", "MEMORY.md", "USER.md", "TOOLS.md", + "TODO.md", "HEARTBEAT.md", "IDENTITY.md", "BOOTSTRAP.md", +]); + +function isBotFile(name: string): boolean { + return BOT_FILES.has(name); +} + +/** Check if a path is a daily note (memory/YYYY-MM-DD*.md) */ +function isDailyNote(path: string): boolean { + return /^memory\/\d{4}-\d{2}-\d{2}.*\.md$/.test(path); +} + +/** Get display label for daily note */ +function getDailyNoteLabel(path: string): { label: string; isToday: boolean; isYesterday: boolean } { + const match = path.match(/memory\/(\d{4}-\d{2}-\d{2})/); + if (!match) return { label: path, isToday: false, isYesterday: false }; + + const dateStr = match[1]; + const today = new Date().toISOString().slice(0, 10); + const yesterday = new Date(Date.now() - 86400000).toISOString().slice(0, 10); + + if (dateStr === today) return { label: "Today", isToday: true, isYesterday: false }; + if (dateStr === yesterday) return { label: "Yesterday", isYesterday: true, isToday: false }; + + // Show date in a readable format for older notes + const date = new Date(dateStr); + const options: Intl.DateTimeFormatOptions = { month: "short", day: "numeric" }; + return { label: date.toLocaleDateString("en-US", options), isToday: false, isYesterday: false }; +} + +/** Local storage key for collapsed state */ +const COLLAPSED_KEY = "memory-viewer-collapsed"; +const SECTIONS_KEY = "memory-viewer-sections"; + +interface SectionState { + coreFiles: boolean; + files: boolean; +} + +function loadCollapsedState(): Set { + try { + const stored = localStorage.getItem(COLLAPSED_KEY); + return stored ? new Set(JSON.parse(stored)) : new Set(); + } catch { + return new Set(); + } +} + +function saveCollapsedState(collapsed: Set) { + try { + localStorage.setItem(COLLAPSED_KEY, JSON.stringify([...collapsed])); + } catch { + // Ignore storage errors + } +} + +function loadSectionState(): SectionState { + try { + const stored = localStorage.getItem(SECTIONS_KEY); + return stored ? JSON.parse(stored) : { coreFiles: false, files: true }; + } catch { + return { coreFiles: false, files: true }; + } +} + +function saveSectionState(state: SectionState) { + try { + localStorage.setItem(SECTIONS_KEY, JSON.stringify(state)); + } catch { + // Ignore storage errors + } +} + +interface FileTreeProps { + nodes: FileNode[]; + activeFile: string; + onSelect: (path: string) => void; +} + +export function FileTree({ nodes, activeFile, onSelect }: FileTreeProps) { + const { t } = useLocale(); + const [collapsed, setCollapsed] = useState>(() => loadCollapsedState()); + const [sections, setSections] = useState(() => loadSectionState()); + + // Persist collapsed state + useEffect(() => { + saveCollapsedState(collapsed); + }, [collapsed]); + + // Persist section state + useEffect(() => { + saveSectionState(sections); + }, [sections]); + + const toggleSection = (key: keyof SectionState) => { + setSections(prev => ({ ...prev, [key]: !prev[key] })); + }; + + const toggleCollapsed = useCallback((path: string) => { + setCollapsed(prev => { + const next = new Set(prev); + if (next.has(path)) { + next.delete(path); + } else { + next.add(path); + } + return next; + }); + }, []); + + const { botFiles, otherNodes } = useMemo(() => { + const bot: FileNode[] = []; + const other: FileNode[] = []; + + for (const node of nodes) { + if (node.type === "file" && isBotFile(node.name)) { + bot.push(node); + } else { + other.push(node); + } + } + + const order = [...BOT_FILES]; + bot.sort((a, b) => order.indexOf(a.name) - order.indexOf(b.name)); + other.sort((a, b) => { + if (a.type === "dir" && b.type !== "dir") return -1; + if (a.type !== "dir" && b.type === "dir") return 1; + return a.name.localeCompare(b.name); + }); + + return { botFiles: bot, otherNodes: other }; + }, [nodes]); + + return ( + + ); +} + +function TreeNode({ node, activeFile, onSelect, depth, collapsed, onToggle }: { + node: FileNode; + activeFile: string; + onSelect: (path: string) => void; + depth: number; + collapsed: Set; + onToggle: (path: string) => void; +}) { + const indent = depth * 12 + 8; + const isCollapsed = collapsed.has(node.path); + // Default: all directories are collapsed unless explicitly expanded + const isOpen = !collapsed.has(node.path); + + if (node.type === "dir") { + return ( +
+ + {!isCollapsed && node.children?.slice().sort((a, b) => { + if (a.type === "dir" && b.type !== "dir") return -1; + if (a.type !== "dir" && b.type === "dir") return 1; + return a.name.localeCompare(b.name); + }).map((child) => ( + + ))} +
+ ); + } + + const isActive = activeFile === node.path; + return ( + + ); +} diff --git a/memory-viewer/src/components/FileViewer.tsx b/memory-viewer/src/components/FileViewer.tsx new file mode 100644 index 0000000..015340f --- /dev/null +++ b/memory-viewer/src/components/FileViewer.tsx @@ -0,0 +1,708 @@ +import { useEffect, useState, useCallback, useRef, useMemo } from "react"; +import ReactMarkdown from "react-markdown"; +import remarkGfm from "remark-gfm"; +import remarkFrontmatter from "remark-frontmatter"; +import rehypeRaw from "rehype-raw"; +import { fetchFile, saveFile, resolveWikilink, ConflictResult } from "../api"; +import { PencilSimple, FloppyDisk, X, Check, WarningCircle, CaretRight, ArrowUp, Copy, Warning, ArrowsClockwise, PenNib, Cube } from "@phosphor-icons/react"; +import { PluginSlot } from "../plugins/PluginSlot"; +import { SensitiveText } from "./SensitiveMask"; +import { useLocale } from "../hooks/useLocale"; +import { lazy, Suspense } from "react"; +import { renderMermaid, THEMES } from "beautiful-mermaid"; +import { useMarkdownTheme } from "../themes"; +import { applyThemeStyles, cleanInlineStyles } from "../themes/apply"; +import mermaid from "mermaid"; +const MarkdownEditor = lazy(() => import("./MarkdownEditor").then(m => ({ default: m.MarkdownEditor }))); + +// Initialize mermaid for different styles +function initMermaid(isDark: boolean, handDrawn: boolean) { + mermaid.initialize({ + startOnLoad: false, + securityLevel: "loose", + theme: isDark ? "dark" : "default", + look: handDrawn ? "handDrawn" : "classic", + fontFamily: handDrawn + ? "Virgil, Segoe Print, Bradley Hand, Chilanka, TSCu_Comic, casual, cursive" + : "Inter, sans-serif", + }); +} + +type MermaidStyle = "normal" | "handDrawn"; + +/** Mermaid diagram renderer with style toggle */ +function MermaidBlock({ code }: { code: string }) { + const [svg, setSvg] = useState(""); + const [error, setError] = useState(""); + const [style, setStyle] = useState("normal"); + const containerRef = useRef(null); + + useEffect(() => { + const isDark = document.documentElement.classList.contains("dark"); + // Generate unique ID for each render to avoid conflicts + const renderId = `mermaid-${Math.random().toString(36).slice(2)}`; + + if (style === "handDrawn") { + // Use native mermaid with hand-drawn look + initMermaid(isDark, true); + console.log("[Mermaid] Rendering hand-drawn style with ID:", renderId); + mermaid.render(renderId, code) + .then((result) => { + console.log("[Mermaid] Render success, svg length:", result.svg?.length); + setSvg(result.svg); + setError(""); + }) + .catch((e) => { + console.error("[Mermaid] Render failed:", e); + setError(e.message || "Failed to render diagram"); + setSvg(""); + }); + } else { + // Use beautiful-mermaid for normal style + const baseTheme = isDark ? THEMES["github-dark"] : THEMES["github-light"]; + const theme = { ...baseTheme, bg: "transparent" }; + renderMermaid(code, theme) + .then((result) => { + setSvg(typeof result === "string" ? result : (result as any).svg || String(result)); + setError(""); + }) + .catch((e) => { + setError(e.message || "Failed to render diagram"); + setSvg(""); + }); + } + }, [code, style]); + + if (error) { + return ( +
+
⚠ Mermaid render error: {error}
+
{code}
+
+ ); + } + + if (!svg) { + return
Rendering diagram...
; + } + + return ( +
+ {/* Style toggle button */} +
+ + +
+
+
+ ); +} + +/** Extract YAML front matter and return { meta, body } */ +interface FrontMatterResult { + meta: Record | null; + metadata: Record | null; + body: string; +} + +function parseFrontMatter(raw: string): FrontMatterResult { + const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/); + if (!match) return { meta: null, metadata: null, body: raw }; + const yamlBlock = match[1]; + const body = match[2]; + const meta: Record = {}; + let metadata: Record | null = null; + for (const line of yamlBlock.split("\n")) { + const idx = line.indexOf(":"); + if (idx > 0) { + const key = line.slice(0, idx).trim(); + const val = line.slice(idx + 1).trim().replace(/^['"]|['"]$/g, ""); + if (!key || !val) continue; + if (key === "metadata") { + try { metadata = JSON.parse(val); } catch { meta[key] = val; } + } else { + meta[key] = val; + } + } + } + return { meta: Object.keys(meta).length ? meta : null, metadata, body }; +} + +/** Recursively wrap string children with SensitiveText and WikiLinks */ +function maskChildren(children: React.ReactNode, onOpenFile?: (path: string) => void): React.ReactNode { + if (typeof children === "string") { + // Check for wikilinks first + if (children.includes("[[")) { + const parts = processWikiLinks(children, onOpenFile); + return parts.map((part, i) => + typeof part === "string" ? {part} : part + ); + } + return {children}; + } + if (Array.isArray(children)) return children.map((c, i) => + typeof c === "string" ? ( + c.includes("[[") ? ( + {processWikiLinks(c, onOpenFile).map((part, j) => + typeof part === "string" ? {part} : part + )} + ) : {c} + ) : c + ); + return children; +} + +/** Shiki highlighter singleton */ +import { createHighlighter, type Highlighter } from "shiki"; + +let highlighterPromise: Promise | null = null; +function getHighlighter(): Promise { + if (!highlighterPromise) { + highlighterPromise = createHighlighter({ + themes: ["github-dark", "github-light"], + langs: ["bash", "javascript", "typescript", "python", "json", "yaml", "markdown", "css", "html", "go", "rust", "sql", "diff", "dockerfile", "toml", "ini", "tsx", "jsx", "java", "c", "cpp", "shell", "ruby", "php", "swift", "kotlin"], + }); + } + return highlighterPromise; +} + +/** Code block with copy button */ +function CodeBlock({ className, children, ...props }: any) { + const { t } = useLocale(); + const [copied, setCopied] = useState(false); + const [html, setHtml] = useState(""); + const match = /language-(\w+)/.exec(className || ""); + const text = String(children).replace(/\n$/, ""); + const isBlock = !!match || text.includes("\n"); + const isDark = document.documentElement.classList.contains("dark"); + + useEffect(() => { + if (!isBlock || !match) return; + if (match[1] === "mermaid") return; + let cancelled = false; + getHighlighter().then((hl) => { + if (cancelled) return; + const lang = hl.getLoadedLanguages().includes(match[1] as any) ? match[1] : "text"; + const result = hl.codeToHtml(text, { + lang, + theme: isDark ? "github-dark" : "github-light", + }); + setHtml(result); + }); + return () => { cancelled = true; }; + }, [text, match?.[1], isDark, isBlock]); + + if (!isBlock) { + if (typeof children === "string") { + return {children}; + } + return {children}; + } + // Mermaid diagrams + if (match && match[1] === "mermaid") { + return ; + } + // Plain text code blocks (no language) - use simple
 for uniform background
+  if (!match) {
+    return (
+      
+ +
+          {text}
+        
+
+ ); + } + return ( +
+ + {html ? ( +
+ ) : ( +
+          {text}
+        
+ )} +
+ ); +} + +/** Breadcrumb path */ +function Breadcrumb({ path, hasChanges, onNavigate }: { path: string; hasChanges: boolean; onNavigate?: (dir: string) => void }) { + const parts = path.split("/"); + return ( + + {hasChanges && "● "} + {parts.map((part, i) => { + const isLast = i === parts.length - 1; + const dir = parts.slice(0, i + 1).join("/"); + return ( + + {i > 0 && } + {isLast ? ( + {part} + ) : ( + + )} + + ); + })} + + ); +} + +/** WikiLink component: renders [[xxx]] as clickable link */ +function WikiLink({ target, onOpenFile }: { target: string; onOpenFile?: (path: string) => void }) { + const [resolved, setResolved] = useState<{ found: boolean; path: string | null } | null>(null); + + useEffect(() => { + resolveWikilink(target).then(setResolved).catch(() => setResolved({ found: false, path: null })); + }, [target]); + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault(); + if (resolved?.found && resolved.path && onOpenFile) { + onOpenFile(resolved.path); + } + }; + + if (!resolved) return [[{target}]]; + + if (resolved.found) { + return ( + + {target} + + ); + } + + return ( + + {target} + + ); +} + +/** Process text to replace [[xxx]] with WikiLink components */ +function processWikiLinks(text: string, onOpenFile?: (path: string) => void): React.ReactNode[] { + const parts: React.ReactNode[] = []; + const regex = /\[\[([^\]]+)\]\]/g; + let lastIndex = 0; + let match; + + while ((match = regex.exec(text)) !== null) { + if (match.index > lastIndex) { + parts.push(text.slice(lastIndex, match.index)); + } + parts.push(); + lastIndex = regex.lastIndex; + } + + if (lastIndex < text.length) { + parts.push(text.slice(lastIndex)); + } + + return parts; +} + +interface FileViewerProps { + filePath: string; + refreshKey?: number; + onNavigate?: (dir: string) => void; + onOpenFile?: (path: string) => void; +} + +export function FileViewer({ filePath, refreshKey, onNavigate, onOpenFile }: FileViewerProps) { + const { t } = useLocale(); + const [content, setContent] = useState(""); + const [editContent, setEditContent] = useState(""); + const [mtime, setMtime] = useState(""); + const [editing, setEditing] = useState(false); + const [loading, setLoading] = useState(true); + const [toast, setToast] = useState(""); + const [conflict, setConflict] = useState(null); + const contentRef = useRef(null); + const [showScrollTop, setShowScrollTop] = useState(false); + const { current: mdTheme } = useMarkdownTheme(); + + useEffect(() => { + setLoading(true); + setEditing(false); + fetchFile(filePath) + .then((data) => { + setContent(data.content); + setEditContent(data.content); + setMtime(data.mtime); + }) + .catch(() => showToast(t("file.failedToLoad"))) + .finally(() => setLoading(false)); + }, [filePath, refreshKey]); + + // Auto-refresh every 10s when not editing (for when WebSocket is unavailable) + useEffect(() => { + if (editing) return; + const interval = setInterval(() => { + fetchFile(filePath).then((data) => { + if (data.mtime !== mtime) { + setContent(data.content); + setEditContent(data.content); + setMtime(data.mtime); + } + }).catch(() => {}); + }, 10000); + return () => clearInterval(interval); + }, [filePath, editing, mtime]); + + const [refreshing, setRefreshing] = useState(false); + const handleRefresh = useCallback(() => { + setRefreshing(true); + fetchFile(filePath) + .then((data) => { + setContent(data.content); + setEditContent(data.content); + setMtime(data.mtime); + showToast(t("file.reloaded") || "Refreshed"); + }) + .catch(() => showToast(t("file.failedToLoad"))) + .finally(() => setRefreshing(false)); + }, [filePath]); + + const showToast = (msg: string) => { + setToast(msg); + setTimeout(() => setToast(""), 2000); + }; + + const hasChanges = editing && editContent !== content; + + const handleSave = useCallback(async (force = false) => { + try { + const result = await saveFile(filePath, editContent, force ? undefined : mtime); + if ("error" in result && result.error === "conflict") { + setConflict(result as ConflictResult); + return; + } + if ("ok" in result && result.ok) { + setContent(editContent); + setMtime(result.mtime); + setEditing(false); + setConflict(null); + showToast(t("file.saved")); + } + } catch { + showToast(t("file.saveFailed")); + } + }, [filePath, editContent, mtime]); + + const handleConflictOverwrite = useCallback(() => { + handleSave(true); + }, [handleSave]); + + const handleConflictReload = useCallback(() => { + if (conflict) { + setContent(conflict.serverContent); + setEditContent(conflict.serverContent); + setMtime(conflict.serverMtime); + setConflict(null); + showToast(t("file.reloaded")); + } + }, [conflict]); + + const handleCancel = () => { + if (hasChanges && !confirm(t("file.discardChanges"))) return; + setEditContent(content); + setEditing(false); + }; + + const [isDark, setIsDark] = useState(() => document.documentElement.classList.contains("dark")); + useEffect(() => { + const obs = new MutationObserver(() => setIsDark(document.documentElement.classList.contains("dark"))); + obs.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] }); + return () => obs.disconnect(); + }, []); + + const handleEdit = () => { + setEditing(true); + }; + + useEffect(() => { + const handler = (e: BeforeUnloadEvent) => { + if (hasChanges) e.preventDefault(); + }; + window.addEventListener("beforeunload", handler); + return () => window.removeEventListener("beforeunload", handler); + }, [hasChanges]); + + const frontMatter = useMemo(() => parseFrontMatter(content), [content]); + + // Apply markdown theme + useEffect(() => { + if (!contentRef.current || editing) return; + const article = contentRef.current.querySelector(".markdown-body"); + if (!article || (!mdTheme.styles && !mdTheme.darkStyles)) { + cleanInlineStyles(article); + return; + } + applyThemeStyles(article as HTMLElement, mdTheme); + }, [content, mdTheme, editing, refreshKey, isDark]); + + const fileStats = useMemo(() => { + const bytes = new Blob([content]).size; + const words = content.trim() ? content.trim().split(/\s+/).length : 0; + const sizeStr = bytes < 1024 ? `${bytes} B` : `${(bytes / 1024).toFixed(1)} KB`; + return { sizeStr, words }; + }, [content]); + + const handleScroll = useCallback((e: React.UIEvent) => { + setShowScrollTop(e.currentTarget.scrollTop > 300); + }, []); + + const scrollToTop = () => { + contentRef.current?.scrollTo({ top: 0, behavior: "smooth" }); + }; + + if (loading) { + return ( +
+
+ {t("file.loading")} +
+ ); + } + + return ( +
+ {/* Toolbar */} +
+
+ + + {new Date(mtime).toLocaleString()} + +
+
+ + {fileStats.sizeStr} + · + {fileStats.words} {t("file.words")} + +
+ {editing ? ( + <> + + + + ) : ( + <> + + + + + )} +
+
+
+ + {/* Content */} +
+ {editing ? ( + Loading editor...
}> + + + ) : ( +
+ {(frontMatter.meta || frontMatter.metadata) && (() => { + const md = frontMatter.metadata; + const cb = md?.clawdbot || md?.openclaw; + const emoji = cb?.emoji || "🧩"; + const requires = cb?.requires; + const extraMeta = frontMatter.meta ? Object.entries(frontMatter.meta).filter(([k]) => k !== "name" && k !== "description") : []; + return ( +
+ {/* Header */} +
+
+ {frontMatter.meta?.name && ( +
+ {emoji} +

{frontMatter.meta.name}

+
+ )} +
+ {frontMatter.meta?.description && ( +

{frontMatter.meta.description}

+ )} +
+ {/* Tags: extra fields + requires */} + {(extraMeta.length > 0 || requires) && ( +
+ {extraMeta.map(([k, v]) => ( + + {k}: {v} + + ))} + {requires?.skills?.map((s: string) => ( + + 🔗 {s} + + ))} + {requires?.tools?.map((t: string) => ( + + 🔧 {t} + + ))} + {requires?.secrets?.map((s: string) => ( + + 🔑 {s} + + ))} +
+ )} +
+ ); + })()} + {children}; + }, + code: CodeBlock, + // Mask in plain text nodes within paragraphs + p({ children }) { + return

{maskChildren(children, onOpenFile)}

; + }, + li({ children }) { + return
  • {maskChildren(children, onOpenFile)}
  • ; + }, + td({ children }) { + return {maskChildren(children, onOpenFile)}; + }, + }} + >{content}
    +
    + )} +
    + + {/* Scroll to top */} + {showScrollTop && !editing && ( + + )} + + {/* Conflict Dialog */} + {conflict && ( +
    +
    +
    + +

    {t("file.conflictTitle")}

    +
    +

    + {t("file.conflictDesc")} +

    +

    + {t("file.conflictTime")}: {new Date(conflict.serverMtime).toLocaleString()} +

    +
    + + + +
    +
    +
    + )} + + {/* Toast */} + {toast &&
    {toast}
    } +
    + ); +} diff --git a/memory-viewer/src/components/MarkdownEditor.tsx b/memory-viewer/src/components/MarkdownEditor.tsx new file mode 100644 index 0000000..803c41e --- /dev/null +++ b/memory-viewer/src/components/MarkdownEditor.tsx @@ -0,0 +1,97 @@ +import { useEffect, useRef, useCallback } from "react"; +import { EditorView, keymap, lineNumbers } from "@codemirror/view"; +import { EditorState } from "@codemirror/state"; +import { markdown, markdownLanguage } from "@codemirror/lang-markdown"; +import { languages } from "@codemirror/language-data"; +import { oneDark } from "@codemirror/theme-one-dark"; +import { defaultKeymap, indentWithTab } from "@codemirror/commands"; +import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language"; + +interface MarkdownEditorProps { + value: string; + onChange: (value: string) => void; + onSave: () => void; + dark?: boolean; +} + +export function MarkdownEditor({ value, onChange, onSave, dark = true }: MarkdownEditorProps) { + const containerRef = useRef(null); + const viewRef = useRef(null); + const onChangeRef = useRef(onChange); + const onSaveRef = useRef(onSave); + onChangeRef.current = onChange; + onSaveRef.current = onSave; + + // Track if update is from external prop change + const externalUpdate = useRef(false); + + const createView = useCallback(() => { + if (!containerRef.current) return; + viewRef.current?.destroy(); + + const themeExtensions = dark + ? [oneDark] + : [syntaxHighlighting(defaultHighlightStyle)]; + + const state = EditorState.create({ + doc: value, + extensions: [ + lineNumbers(), + EditorView.lineWrapping, + keymap.of([ + ...defaultKeymap, + indentWithTab, + { key: "Mod-s", run: () => { onSaveRef.current(); return true; } }, + ]), + markdown({ base: markdownLanguage, codeLanguages: languages }), + ...themeExtensions, + EditorView.updateListener.of((update) => { + if (update.docChanged && !externalUpdate.current) { + onChangeRef.current(update.state.doc.toString()); + } + }), + EditorView.theme({ + "&": { height: "100%", fontSize: "14px" }, + ".cm-scroller": { overflow: "auto" }, + ".cm-content": { fontFamily: "'JetBrains Mono', 'Fira Code', monospace" }, + ".cm-gutters": { borderRight: "none" }, + }), + ], + }); + + viewRef.current = new EditorView({ state, parent: containerRef.current }); + }, [dark]); + + // Create/recreate on dark change + useEffect(() => { + createView(); + return () => viewRef.current?.destroy(); + }, [createView]); + + // Sync external value changes + useEffect(() => { + const view = viewRef.current; + if (!view) return; + const current = view.state.doc.toString(); + if (value !== current) { + externalUpdate.current = true; + view.dispatch({ + changes: { from: 0, to: current.length, insert: value }, + }); + externalUpdate.current = false; + } + }, [value]); + + // Focus on mount + useEffect(() => { + setTimeout(() => viewRef.current?.focus(), 50); + }, []); + + return ( +
    + ); +} diff --git a/memory-viewer/src/components/SearchPanel.tsx b/memory-viewer/src/components/SearchPanel.tsx new file mode 100644 index 0000000..741dc30 --- /dev/null +++ b/memory-viewer/src/components/SearchPanel.tsx @@ -0,0 +1,245 @@ +import { useState, useRef, useEffect, useCallback } from "react"; +import { searchFiles, semanticSearch, fetchCapabilities, type SearchResult, type SemanticResult } from "../api"; +import { MagnifyingGlass, FileText, TextAa, Brain } from "@phosphor-icons/react"; +import { useLocale } from "../hooks/useLocale"; + +type SearchMode = "text" | "bm25" | "vector"; + +interface SearchPanelProps { + onSelect: (path: string) => void; + onClose: () => void; +} + +export function SearchPanel({ onSelect, onClose }: SearchPanelProps) { + const { t } = useLocale(); + const [query, setQuery] = useState(""); + const [mode, setMode] = useState("text"); + const [textResults, setTextResults] = useState([]); + const [semanticResults, setSemanticResults] = useState([]); + const [loading, setLoading] = useState(false); + const [hasBm25, setHasBm25] = useState(false); + const [hasVector, setHasVector] = useState(false); + const inputRef = useRef(null); + const timerRef = useRef>(); + + useEffect(() => { + inputRef.current?.focus(); + fetchCapabilities().then((cap) => { + setHasBm25(cap.qmdBm25); + setHasVector(cap.qmdVector); + }); + }, []); + + useEffect(() => { + const handler = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", handler); + return () => window.removeEventListener("keydown", handler); + }, [onClose]); + + const doSearch = useCallback((q: string, m: SearchMode) => { + if (q.length < 2) { + setTextResults([]); + setSemanticResults([]); + return; + } + setLoading(true); + if (m === "text") { + searchFiles(q) + .then(setTextResults) + .catch(() => setTextResults([])) + .finally(() => setLoading(false)); + } else { + semanticSearch(q, m) + .then(setSemanticResults) + .catch(() => setSemanticResults([])) + .finally(() => setLoading(false)); + } + }, []); + + const handleInput = (val: string) => { + setQuery(val); + clearTimeout(timerRef.current); + const delay = mode === "text" ? 300 : 500; + timerRef.current = setTimeout(() => doSearch(val, mode), delay); + }; + + const handleModeChange = (m: SearchMode) => { + setMode(m); + setTextResults([]); + setSemanticResults([]); + if (query.length >= 2) { + setLoading(true); + const delay = m === "text" ? 0 : 100; + setTimeout(() => doSearch(query, m), delay); + } + }; + + const handleSelect = (path: string) => { + onSelect(path); + onClose(); + }; + + const totalMatches = mode === "text" + ? textResults.reduce((s, r) => s + r.matches.length, 0) + : semanticResults.length; + + const modeButtons: { key: SearchMode; icon: typeof TextAa; label: string }[] = [ + { key: "text", icon: TextAa, label: t("search.modeText") }, + ...(hasBm25 ? [{ key: "bm25" as SearchMode, icon: MagnifyingGlass, label: "BM25" }] : []), + ...(hasVector ? [{ key: "vector" as SearchMode, icon: Brain, label: t("search.modeSemantic") }] : []), + ]; + + return ( +
    +
    e.stopPropagation()} + > + {/* Search input */} +
    + + handleInput(e.target.value)} + placeholder={t("search.placeholder")} + className="flex-1 bg-transparent outline-none text-base" + style={{ color: "var(--text-primary)" }} + /> + {loading && ( +
    + {mode !== "text" && 搜索中…} +
    +
    + )} + + ESC + +
    + + {/* Mode toggle — only show if QMD is available */} + {(hasBm25 || hasVector) &&
    + {modeButtons.map(({ key, icon: Icon, label }) => ( + + ))} +
    } + + {/* Results */} +
    + {loading && query.length >= 2 && mode !== "text" && ( +
    + {[1, 2, 3].map((i) => ( +
    +
    +
    +
    +
    + ))} +
    + )} + {query.length >= 2 && totalMatches === 0 && !loading && ( +
    + {t("search.noResults")} “{query}” +
    + )} + + {/* Text search results */} + {mode === "text" && textResults.map((r) => ( + + ))} + + {/* Semantic search results */} + {mode !== "text" && semanticResults.map((r, idx) => ( + + ))} +
    + + {/* Footer */} + {totalMatches > 0 && ( +
    + {mode === "text" + ? `${textResults.length} ${t("search.files")} · ${totalMatches} ${t("search.matches")}` + : `${semanticResults.length} ${t("search.results")} · ${mode === "vector" ? "🧠" : "📊"} ${mode.toUpperCase()}` + } +
    + )} +
    +
    + ); +} + +function highlightMatch(text: string, query: string) { + const lower = text.toLowerCase(); + const idx = lower.indexOf(query.toLowerCase()); + if (idx === -1) return {text}; + return ( + + {text.slice(0, idx)} + {text.slice(idx, idx + query.length)} + {text.slice(idx + query.length)} + + ); +} diff --git a/memory-viewer/src/components/SensitiveMask.tsx b/memory-viewer/src/components/SensitiveMask.tsx new file mode 100644 index 0000000..27c9858 --- /dev/null +++ b/memory-viewer/src/components/SensitiveMask.tsx @@ -0,0 +1,77 @@ +import { useSensitive } from "../hooks/useSensitive"; + +/** + * Patterns that match API tokens, keys, secrets, passwords, etc. + * Each match will be blurred when sensitive mode is on. + */ +const SENSITIVE_PATTERNS = [ + // Generic API keys/tokens (long hex/alphanum strings after key-like labels) + /(?<=(?:key|token|secret|password|apikey|api_key|api-key|bearer|authorization)\s*[:=]\s*)[A-Za-z0-9_\-./+]{16,}/gi, + // Specific formats + /pplx-[A-Za-z0-9]{40,}/g, // Perplexity + /sk-[A-Za-z0-9_\-]{32,}/g, // OpenAI-style + /re_[A-Za-z0-9_]{20,}/g, // Resend + /ghp_[A-Za-z0-9]{36,}/g, // GitHub PAT + /gho_[A-Za-z0-9]{36,}/g, // GitHub OAuth + /xai-[A-Za-z0-9]{40,}/g, // xAI + /GOCSPX-[A-Za-z0-9_\-]{20,}/g, // Google client secret + /AIza[A-Za-z0-9_\-]{30,}/g, // Google API key + /[0-9a-f]{48,}/g, // Long hex strings (48+ chars, likely tokens) + /eyJ[A-Za-z0-9_\-]{20,}\.[A-Za-z0-9_\-]+/g, // JWT tokens +]; + +/** + * Replace sensitive substrings in text with masked spans. + */ +export function maskSensitiveText(text: string, hidden: boolean): (string | JSX.Element)[] { + if (!hidden) return [text]; + + // Collect all match ranges + const ranges: { start: number; end: number }[] = []; + for (const pattern of SENSITIVE_PATTERNS) { + const re = new RegExp(pattern.source, pattern.flags); + let m; + while ((m = re.exec(text)) !== null) { + ranges.push({ start: m.index, end: m.index + m[0].length }); + } + } + + if (ranges.length === 0) return [text]; + + // Merge overlapping ranges + ranges.sort((a, b) => a.start - b.start); + const merged: { start: number; end: number }[] = [ranges[0]]; + for (let i = 1; i < ranges.length; i++) { + const last = merged[merged.length - 1]; + if (ranges[i].start <= last.end) { + last.end = Math.max(last.end, ranges[i].end); + } else { + merged.push(ranges[i]); + } + } + + // Build result + const result: (string | JSX.Element)[] = []; + let pos = 0; + for (const { start, end } of merged) { + if (start > pos) result.push(text.slice(pos, start)); + result.push( + + {text.slice(start, end)} + + ); + pos = end; + } + if (pos < text.length) result.push(text.slice(pos)); + return result; +} + +/** + * Wrap around inline text nodes to auto-mask sensitive content. + */ +export function SensitiveText({ children }: { children: string }) { + const { hidden } = useSensitive(); + if (typeof children !== "string") return <>{children}; + const parts = maskSensitiveText(children, hidden); + return <>{parts}; +} diff --git a/memory-viewer/src/components/SettingsPage.tsx b/memory-viewer/src/components/SettingsPage.tsx new file mode 100644 index 0000000..0429c18 --- /dev/null +++ b/memory-viewer/src/components/SettingsPage.tsx @@ -0,0 +1,305 @@ +import { useState, useEffect } from "react"; +import { fetchSettings, saveSettings, testEmbeddingConnection, type EmbeddingSettings, getBaseUrl } from "../api"; +import { Gear, FloppyDisk, Lightning, CheckCircle, XCircle, Database, Files } from "@phosphor-icons/react"; +import { useLocale } from "../hooks/useLocale"; + +export function SettingsPage() { + const { t } = useLocale(); + const [settings, setSettings] = useState({ + enabled: false, + apiUrl: "", + apiKey: "", + model: "", + apiKeySet: false + }); + const [loading, setLoading] = useState(true); + const [saving, setSaving] = useState(false); + const [testing, setTesting] = useState(false); + const [testResult, setTestResult] = useState<{ success: boolean; message?: string } | null>(null); + const [touched, setTouched] = useState(false); + const [embStats, setEmbStats] = useState<{ cachedFiles: number; totalFiles: number; coverage: number; dbSize: number; model: string } | null>(null); + + useEffect(() => { + loadSettings(); + loadEmbStats(); + }, []); + + const loadEmbStats = async () => { + try { + const r = await fetch(`${getBaseUrl()}/api/settings/embedding-stats`); + setEmbStats(await r.json()); + } catch {} + }; + + const loadSettings = async () => { + try { + const data = await fetchSettings(); + setSettings(data.embedding); + } catch (err) { + console.error("Failed to load settings:", err); + } finally { + setLoading(false); + } + }; + + const handleChange = (field: keyof EmbeddingSettings, value: any) => { + setSettings(prev => ({ ...prev, [field]: value })); + setTouched(true); + }; + + const handleSave = async () => { + setSaving(true); + try { + await saveSettings({ embedding: settings }); + setTouched(false); + // Reload to get the safe version (with apiKey hidden) + await loadSettings(); + } catch (err) { + console.error("Failed to save settings:", err); + } finally { + setSaving(false); + } + }; + + const handleTest = async () => { + setTesting(true); + setTestResult(null); + try { + const result = await testEmbeddingConnection(settings); + setTestResult({ + success: result.success, + message: result.error || "Connection successful" + }); + } catch (err: any) { + setTestResult({ + success: false, + message: err.message || "Test failed" + }); + } finally { + setTesting(false); + } + }; + + if (loading) { + return ( +
    +
    + Loading settings... +
    + ); + } + + return ( +
    +

    + {t("settings.title") || "⚙️ Settings"} +

    + + {/* Embedding Settings */} +
    +

    + {t("settings.embedding.title") || "向量搜索 (Embedding)"} +

    + +
    + {/* Enable/Disable toggle */} +
    +
    +
    + {t("settings.embedding.enable") || "启用向量搜索"} +
    +
    + {t("settings.embedding.enableDesc") || "使用 OpenAI Embeddings API 进行语义搜索"} +
    +
    +
    + + {/* Embedding Stats */} + {settings.enabled && embStats && embStats.cachedFiles > 0 && ( +
    +
    + + Embedding 缓存状态 +
    +
    +
    +
    + {embStats.cachedFiles}/{embStats.totalFiles} +
    +
    已缓存文件
    +
    +
    +
    + {embStats.coverage}% +
    +
    覆盖率
    +
    +
    +
    + {embStats.dbSize > 1048576 ? `${(embStats.dbSize / 1048576).toFixed(1)}MB` : `${Math.round(embStats.dbSize / 1024)}KB`} +
    +
    缓存大小
    +
    +
    + {/* Coverage bar */} +
    +
    +
    +
    + 模型: {embStats.model} +
    +
    + )} + + {/* Save Button */} +
    + +
    +
    +
    + ); +} \ No newline at end of file diff --git a/memory-viewer/src/components/SkillsPage.tsx b/memory-viewer/src/components/SkillsPage.tsx new file mode 100644 index 0000000..5f11782 --- /dev/null +++ b/memory-viewer/src/components/SkillsPage.tsx @@ -0,0 +1,67 @@ +import { PuzzlePiece, ArrowSquareOut, FileText } from "@phosphor-icons/react"; +import type { SkillInfo } from "../api"; +import { useLocale } from "../hooks/useLocale"; + +interface SkillsPageProps { + skills: SkillInfo[]; + onOpenFile: (path: string) => void; +} + +export function SkillsPage({ skills, onOpenFile }: SkillsPageProps) { + const { t } = useLocale(); + + return ( +
    +
    + {/* Header */} +
    +

    + + {t("skills.title") || "Skills"} +

    +

    + {t("skills.description") || "Agent capabilities and extensions"} +

    +
    + + {/* Skills Grid */} + {skills.length === 0 ? ( +
    + {t("skills.empty") || "No skills found"} +
    + ) : ( +
    + {skills.map((skill) => ( + + ))} +
    + )} +
    +
    + ); +} diff --git a/memory-viewer/src/components/Tags.tsx b/memory-viewer/src/components/Tags.tsx new file mode 100644 index 0000000..53f9439 --- /dev/null +++ b/memory-viewer/src/components/Tags.tsx @@ -0,0 +1,311 @@ +import { useEffect, useMemo, useState } from "react"; +import { Hash, FileText, CaretRight, Clock, Tag as TagIcon, X } from "@phosphor-icons/react"; +import { fetchTags, fetchFilesByTag, type TagInfo, type FileWithTags } from "../api"; +import { useLocale } from "../hooks/useLocale"; + +interface Props { + onOpenFile: (path: string) => void; +} + +export function Tags({ onOpenFile }: Props) { + const { t } = useLocale(); + const [tags, setTags] = useState([]); + const [selectedTag, setSelectedTag] = useState(null); + const [files, setFiles] = useState([]); + const [loading, setLoading] = useState(true); + const [filesLoading, setFilesLoading] = useState(false); + + // Load all tags on mount + useEffect(() => { + (async () => { + setLoading(true); + try { + const data = await fetchTags(); + setTags(data); + } catch (e) { + console.error("Tags load failed:", e); + } finally { + setLoading(false); + } + })(); + }, []); + + // Load files when tag selected + useEffect(() => { + if (!selectedTag) { + setFiles([]); + return; + } + (async () => { + setFilesLoading(true); + try { + const data = await fetchFilesByTag(selectedTag); + setFiles(data); + } catch (e) { + console.error("Files by tag load failed:", e); + } finally { + setFilesLoading(false); + } + })(); + }, [selectedTag]); + + // Calculate tag sizes for cloud display + const tagSizes = useMemo(() => { + if (tags.length === 0) return []; + const maxCount = Math.max(...tags.map(t => t.count)); + const minCount = Math.min(...tags.map(t => t.count)); + const range = maxCount - minCount || 1; + + return tags.map(tag => { + // Normalize to 1-5 scale + const size = 1 + ((tag.count - minCount) / range) * 4; + return { ...tag, size: Math.round(size) }; + }); + }, [tags]); + + // Group tags by first letter/category + const groupedTags = useMemo(() => { + const groups: Record = {}; + for (const tag of tags) { + const firstChar = tag.name.charAt(0); + const key = /[\u4e00-\u9fa5]/.test(firstChar) ? "中文" : + /[a-zA-Z]/.test(firstChar) ? firstChar.toUpperCase() : + "#"; + (groups[key] ||= []).push(tag); + } + return Object.entries(groups).sort(([a], [b]) => a.localeCompare(b)); + }, [tags]); + + const stats = useMemo(() => ({ + totalTags: tags.length, + totalFiles: tags.reduce((s, t) => s + t.count, 0), + avgPerTag: tags.length > 0 ? (tags.reduce((s, t) => s + t.count, 0) / tags.length).toFixed(1) : "0", + }), [tags]); + + // Get color based on tag size + const getTagColor = (size: number) => { + const colors = [ + "var(--text-faint)", + "var(--text-muted)", + "var(--text-secondary)", + "var(--accent)", + "#3b82f6", + ]; + return colors[size - 1] || colors[0]; + }; + + const getTagSize = (size: number) => { + const sizes = ["0.75rem", "0.875rem", "1rem", "1.125rem", "1.25rem"]; + return sizes[size - 1] || sizes[0]; + }; + + return ( +
    + {/* Header */} +
    +
    +
    + +
    +
    +

    + {t("tags.title", "标签")} +

    +

    + {t("tags.subtitle", "按主题浏览记忆")} +

    +
    +
    + + {selectedTag && ( + + )} +
    + + {/* Stats bar */} +
    +
    + + {stats.totalTags} + {t("tags.totalTags", "标签")} +
    +
    + + {stats.totalFiles} + {t("tags.totalFiles", "文件")} +
    +
    + + {stats.avgPerTag} + {t("tags.avgPerTag", "平均")} +
    + {loading && ( +
    + )} +
    + + {/* Selected tag files view */} + {selectedTag ? ( +
    +
    + {t("tags.showing", "显示标签")} + + {selectedTag} + + + ({files.length} {t("tags.files", "个文件")}) + +
    + + {filesLoading ? ( +
    +
    +
    + ) : files.length === 0 ? ( +
    + +

    {t("tags.noFiles", "没有找到相关文件")}

    +
    + ) : ( +
    + {files.map((file, i) => ( + + ))} +
    + )} +
    + ) : ( + /* Tag cloud view */ +
    + {/* Popular tags cloud */} + {tagSizes.length > 0 && ( +
    +

    + + {t("tags.popular", "热门标签")} +

    +
    + {tagSizes.slice(0, 30).map(({ name, count, size }) => ( + + ))} +
    +
    + )} + + {/* Grouped tags list */} +
    + {groupedTags.map(([key, groupTags]) => ( +
    +

    + + {key} + + {groupTags.length} {t("tags.tags", "个标签")} +

    +
    + {groupTags.map(({ name, count }) => ( + + ))} +
    +
    + ))} +
    + + {tags.length === 0 && !loading && ( +
    + +

    {t("tags.empty", "暂无标签")}

    +

    {t("tags.emptyHint", "在 Markdown 中使用 ## 标题 或 #标签 来创建标签")}

    +
    + )} +
    + )} +
    + ); +} diff --git a/memory-viewer/src/components/Timeline.tsx b/memory-viewer/src/components/Timeline.tsx new file mode 100644 index 0000000..2b95de3 --- /dev/null +++ b/memory-viewer/src/components/Timeline.tsx @@ -0,0 +1,242 @@ +import { useEffect, useMemo, useState } from "react"; +import { Calendar, FileText, CaretDown, CaretRight, Clock, Hash, Notebook, TextAa } from "@phosphor-icons/react"; +import { fetchTimeline, type TimelineEntry } from "../api"; +import { useLocale } from "../hooks/useLocale"; + +type DiaryEntry = TimelineEntry; + +interface MonthGroup { + key: string; + label: string; + entries: DiaryEntry[]; + totalChars: number; +} + +interface Props { + onOpenFile: (path: string) => void; +} + +export function Timeline({ onOpenFile }: Props) { + const { t, locale } = useLocale(); + const [entries, setEntries] = useState([]); + const [loading, setLoading] = useState(true); + const [expanded, setExpanded] = useState>(new Set()); + const [tagFilter, setTagFilter] = useState(null); + + useEffect(() => { + (async () => { + setLoading(true); + try { + const data = await fetchTimeline(); + setEntries(data); + const now = new Date().toISOString().slice(0, 7); + setExpanded(new Set([now])); + } catch (e) { + console.error("Timeline load failed:", e); + } finally { + setLoading(false); + } + })(); + }, []); + + const filtered = useMemo(() => { + if (!tagFilter) return entries; + return entries.filter(e => e.tags.some(t => t.includes(tagFilter))); + }, [entries, tagFilter]); + + const months = useMemo((): MonthGroup[] => { + const map: Record = {}; + for (const e of filtered) { + const k = e.date.slice(0, 7); + (map[k] ||= []).push(e); + } + const zhMonths = ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]; + const enMonths = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]; + return Object.entries(map) + .sort(([a], [b]) => b.localeCompare(a)) + .map(([k, entries]) => { + const [y, m] = k.split("-"); + const mi = parseInt(m) - 1; + const label = locale.startsWith("zh") ? `${y} 年 ${zhMonths[mi]}` : `${enMonths[mi]} ${y}`; + return { + key: k, + label, + entries: entries.sort((a, b) => b.date.localeCompare(a.date)), + totalChars: entries.reduce((s, e) => s + e.charCount, 0), + }; + }); + }, [filtered, locale]); + + const allTags = useMemo(() => { + const count: Record = {}; + for (const e of entries) for (const t of e.tags) count[t] = (count[t] || 0) + 1; + return Object.entries(count).sort(([, a], [, b]) => b - a).slice(0, 12); + }, [entries]); + + const stats = useMemo(() => ({ + total: entries.length, + chars: entries.reduce((s, e) => s + e.charCount, 0), + months: new Set(entries.map(e => e.date.slice(0, 7))).size, + }), [entries]); + + const toggle = (k: string) => setExpanded(prev => { + const next = new Set(prev); + next.has(k) ? next.delete(k) : next.add(k); + return next; + }); + + const weekday = (d: string) => { + const days = locale.startsWith("zh") + ? ["日", "一", "二", "三", "四", "五", "六"] + : ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]; + return days[new Date(d + "T00:00:00").getDay()]; + }; + + const fmtChars = (n: number) => n >= 10000 ? `${(n / 10000).toFixed(1)}万` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n); + + return ( +
    + {/* Stats bar */} +
    +
    + + {stats.total} + {t("timeline.entries")} +
    +
    + + {fmtChars(stats.chars)} + {t("timeline.chars")} +
    +
    + + {stats.months} + {t("timeline.months")} +
    + {loading && ( +
    + )} +
    + + {/* Tags */} + {allTags.length > 0 && ( +
    + + {tagFilter && ( + + )} + {allTags.map(([tag, count]) => ( + + ))} +
    + )} + + {/* Month groups */} + {months.length === 0 && !loading && ( +
    + +

    {t("timeline.empty")}

    +
    + )} + +
    + {months.map(group => ( +
    + {/* Month header */} + + + {/* Entries */} + {expanded.has(group.key) && ( +
    + {group.entries.map((entry, i) => ( + + ))} +
    + )} +
    + ))} +
    +
    + ); +} diff --git a/memory-viewer/src/hooks/useAgents.ts b/memory-viewer/src/hooks/useAgents.ts new file mode 100644 index 0000000..40927de --- /dev/null +++ b/memory-viewer/src/hooks/useAgents.ts @@ -0,0 +1,60 @@ +import { useState, useCallback, useEffect } from "react"; +import { fetchAgents, setCurrentAgent, type AgentInfo } from "../api"; + +const STORAGE_KEY = "memory-viewer-selected-agent"; + +export function useAgents() { + const [agents, setAgents] = useState([]); + const [selectedAgentId, setSelectedAgentId] = useState(() => { + return localStorage.getItem(STORAGE_KEY) || "default"; + }); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + const loadAgents = useCallback(async () => { + try { + setLoading(true); + const data = await fetchAgents(); + setAgents(data); + setError(null); + + // Validate selected agent still exists + const exists = data.find((a) => a.id === selectedAgentId); + if (!exists && data.length > 0) { + // Fall back to default or first agent + const defaultAgent = data.find((a) => a.id === "default") || data[0]; + setSelectedAgentId(defaultAgent.id); + setCurrentAgent(defaultAgent.id); + localStorage.setItem(STORAGE_KEY, defaultAgent.id); + } else if (exists) { + setCurrentAgent(selectedAgentId); + } + } catch (e: any) { + setError(e.message || "Failed to load agents"); + } finally { + setLoading(false); + } + }, [selectedAgentId]); + + const selectAgent = useCallback((agentId: string) => { + setSelectedAgentId(agentId); + setCurrentAgent(agentId); + localStorage.setItem(STORAGE_KEY, agentId); + }, []); + + useEffect(() => { + loadAgents(); + }, [loadAgents]); + + const selectedAgent = agents.find((a) => a.id === selectedAgentId) || agents[0]; + + return { + agents, + selectedAgent, + selectedAgentId, + selectAgent, + loading, + error, + refresh: loadAgents, + }; +} diff --git a/memory-viewer/src/hooks/useConnections.ts b/memory-viewer/src/hooks/useConnections.ts new file mode 100644 index 0000000..c08f83a --- /dev/null +++ b/memory-viewer/src/hooks/useConnections.ts @@ -0,0 +1,120 @@ +import { useState, useCallback, useEffect, useRef } from "react"; + +export interface BotConnection { + id: string; + name: string; + url: string; // "" for local + token?: string; + isLocal?: boolean; +} + +const STORAGE_KEY = "memory-viewer-connections"; +const ACTIVE_KEY = "memory-viewer-active-connection"; + +const LOCAL_CONNECTION: BotConnection = { + id: "local", + name: "Local", + url: "", + isLocal: true, +}; + +function loadConnections(): BotConnection[] { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (raw) { + const list = JSON.parse(raw) as BotConnection[]; + // Ensure local is always first + if (!list.find((c) => c.id === "local")) { + list.unshift(LOCAL_CONNECTION); + } + return list; + } + } catch { /* ignore */ } + return [LOCAL_CONNECTION]; +} + +function saveConnections(list: BotConnection[]) { + localStorage.setItem(STORAGE_KEY, JSON.stringify(list)); +} + +export function useConnections() { + const [connections, setConnections] = useState(loadConnections); + const [activeId, setActiveId] = useState(() => { + return localStorage.getItem(ACTIVE_KEY) || "local"; + }); + const [statuses, setStatuses] = useState>({}); + const intervalRef = useRef>(); + + const active = connections.find((c) => c.id === activeId) || connections[0]; + + const addConnection = useCallback((conn: Omit) => { + const id = `bot-${Date.now()}`; + setConnections((prev) => { + const next = [...prev, { ...conn, id }]; + saveConnections(next); + return next; + }); + }, []); + + const updateConnection = useCallback((id: string, updates: Partial) => { + setConnections((prev) => { + const next = prev.map((c) => (c.id === id ? { ...c, ...updates } : c)); + saveConnections(next); + return next; + }); + }, []); + + const removeConnection = useCallback((id: string) => { + if (id === "local") return; + setConnections((prev) => { + const next = prev.filter((c) => c.id !== id); + saveConnections(next); + return next; + }); + setActiveId((prev) => (prev === id ? "local" : prev)); + }, []); + + const switchTo = useCallback((id: string) => { + setActiveId(id); + localStorage.setItem(ACTIVE_KEY, id); + }, []); + + // Check connection statuses + const checkStatuses = useCallback(async () => { + const results: Record = {}; + await Promise.all( + connections.map(async (conn) => { + if (conn.isLocal) { + results[conn.id] = true; + return; + } + try { + const r = await fetch(`${conn.url.replace(/\/+$/, "")}/api/system`, { + signal: AbortSignal.timeout(5000), + }); + results[conn.id] = r.ok; + } catch { + results[conn.id] = false; + } + }) + ); + setStatuses(results); + }, [connections]); + + useEffect(() => { + checkStatuses(); + intervalRef.current = setInterval(checkStatuses, 30000); + return () => clearInterval(intervalRef.current); + }, [checkStatuses]); + + return { + connections, + active, + statuses, + addConnection, + updateConnection, + removeConnection, + switchTo, + checkStatuses, + }; +} diff --git a/memory-viewer/src/hooks/useLocale.ts b/memory-viewer/src/hooks/useLocale.ts new file mode 100644 index 0000000..90e8bf0 --- /dev/null +++ b/memory-viewer/src/hooks/useLocale.ts @@ -0,0 +1,40 @@ +import { createContext, useContext, useState, useCallback, useMemo } from "react"; +import { type Locale, translate } from "../i18n"; + +interface LocaleContextValue { + locale: Locale; + setLocale: (l: Locale) => void; + toggleLocale: () => void; + t: (key: string) => string; +} + +function getInitialLocale(): Locale { + try { + const saved = localStorage.getItem("mv-locale"); + if (saved === "zh" || saved === "en") return saved; + } catch {} + return "en"; +} + +export const LocaleContext = createContext(null!); + +export function useLocaleState() { + const [locale, setLocaleRaw] = useState(getInitialLocale); + + const setLocale = useCallback((l: Locale) => { + setLocaleRaw(l); + try { localStorage.setItem("mv-locale", l); } catch {} + }, []); + + const toggleLocale = useCallback(() => { + setLocale(locale === "en" ? "zh" : "en"); + }, [locale, setLocale]); + + const t = useCallback((key: string) => translate(key, locale), [locale]); + + return useMemo(() => ({ locale, setLocale, toggleLocale, t }), [locale, setLocale, toggleLocale, t]); +} + +export function useLocale() { + return useContext(LocaleContext); +} diff --git a/memory-viewer/src/hooks/useResizableSidebar.ts b/memory-viewer/src/hooks/useResizableSidebar.ts new file mode 100644 index 0000000..c407604 --- /dev/null +++ b/memory-viewer/src/hooks/useResizableSidebar.ts @@ -0,0 +1,75 @@ +import { useState, useEffect, useCallback, useRef } from "react"; + +const SIDEBAR_WIDTH_KEY = "memory-viewer-sidebar-width"; +const DEFAULT_WIDTH = 320; +const MIN_WIDTH = 200; +const MAX_WIDTH = 600; + +export function useResizableSidebar() { + const [width, setWidth] = useState(() => { + const saved = localStorage.getItem(SIDEBAR_WIDTH_KEY); + return saved ? Number(saved) : DEFAULT_WIDTH; + }); + const isDragging = useRef(false); + const startX = useRef(0); + const startWidth = useRef(0); + + useEffect(() => { + localStorage.setItem(SIDEBAR_WIDTH_KEY, String(width)); + }, [width]); + + const handleMove = useCallback((clientX: number) => { + if (!isDragging.current) return; + const newWidth = Math.min(MAX_WIDTH, Math.max(MIN_WIDTH, startWidth.current + clientX - startX.current)); + setWidth(newWidth); + }, []); + + const handleEnd = useCallback(() => { + isDragging.current = false; + document.body.style.cursor = ""; + document.body.style.userSelect = ""; + }, []); + + // Mouse events + const onMouseDown = useCallback((e: React.MouseEvent) => { + e.preventDefault(); + isDragging.current = true; + startX.current = e.clientX; + startWidth.current = width; + + const onMouseMove = (e: MouseEvent) => handleMove(e.clientX); + const onMouseUp = () => { + handleEnd(); + document.removeEventListener("mousemove", onMouseMove); + document.removeEventListener("mouseup", onMouseUp); + }; + + document.body.style.cursor = "col-resize"; + document.body.style.userSelect = "none"; + document.addEventListener("mousemove", onMouseMove); + document.addEventListener("mouseup", onMouseUp); + }, [width, handleMove, handleEnd]); + + // Touch events + const onTouchStart = useCallback((e: React.TouchEvent) => { + if (e.touches.length !== 1) return; + isDragging.current = true; + startX.current = e.touches[0].clientX; + startWidth.current = width; + + const onTouchMove = (e: TouchEvent) => { + if (e.touches.length !== 1) return; + handleMove(e.touches[0].clientX); + }; + const onTouchEnd = () => { + handleEnd(); + document.removeEventListener("touchmove", onTouchMove); + document.removeEventListener("touchend", onTouchEnd); + }; + + document.addEventListener("touchmove", onTouchMove, { passive: true }); + document.addEventListener("touchend", onTouchEnd); + }, [width, handleMove, handleEnd]); + + return { width, onMouseDown, onTouchStart, MIN_WIDTH, MAX_WIDTH }; +} diff --git a/memory-viewer/src/hooks/useSensitive.ts b/memory-viewer/src/hooks/useSensitive.ts new file mode 100644 index 0000000..7ada6e5 --- /dev/null +++ b/memory-viewer/src/hooks/useSensitive.ts @@ -0,0 +1,23 @@ +import { useState, useEffect, createContext, useContext } from "react"; + +const SensitiveContext = createContext({ hidden: true, toggle: () => {} }); + +export function useSensitiveState() { + const [hidden, setHidden] = useState(() => { + if (typeof window !== "undefined") { + return localStorage.getItem("sensitive-hidden") !== "false"; + } + return true; + }); + + useEffect(() => { + localStorage.setItem("sensitive-hidden", String(hidden)); + }, [hidden]); + + const toggle = () => setHidden((h) => !h); + + return { hidden, toggle }; +} + +export const SensitiveProvider = SensitiveContext.Provider; +export const useSensitive = () => useContext(SensitiveContext); diff --git a/memory-viewer/src/hooks/useTheme.ts b/memory-viewer/src/hooks/useTheme.ts new file mode 100644 index 0000000..fef3f6e --- /dev/null +++ b/memory-viewer/src/hooks/useTheme.ts @@ -0,0 +1,28 @@ +import { useState, useEffect } from "react"; + +type Theme = "dark" | "light"; + +export function useTheme() { + const [theme, setTheme] = useState(() => { + if (typeof window !== "undefined") { + return (localStorage.getItem("theme") as Theme) || "dark"; + } + return "dark"; + }); + + useEffect(() => { + const root = document.documentElement; + if (theme === "dark") { + root.classList.add("dark"); + root.classList.remove("light"); + } else { + root.classList.add("light"); + root.classList.remove("dark"); + } + localStorage.setItem("theme", theme); + }, [theme]); + + const toggle = () => setTheme((t) => (t === "dark" ? "light" : "dark")); + + return { theme, toggle }; +} diff --git a/memory-viewer/src/hooks/useWebSocket.ts b/memory-viewer/src/hooks/useWebSocket.ts new file mode 100644 index 0000000..a30a73c --- /dev/null +++ b/memory-viewer/src/hooks/useWebSocket.ts @@ -0,0 +1,44 @@ +import { useEffect, useRef, useCallback } from "react"; + +type MessageHandler = (data: { type: string; event: string; path: string }) => void; + +/** + * Auto-reconnecting WebSocket hook for live file-change notifications. + * Supports dynamic URL for remote connections. + */ +export function useWebSocket(onMessage: MessageHandler, remoteBaseUrl = "") { + const wsRef = useRef(null); + const cbRef = useRef(onMessage); + cbRef.current = onMessage; + + const connect = useCallback(() => { + let wsUrl: string; + if (remoteBaseUrl) { + // Convert http(s)://host:port to ws(s)://host:port/ws + const url = new URL(remoteBaseUrl); + const proto = url.protocol === "https:" ? "wss:" : "ws:"; + wsUrl = `${proto}//${url.host}/ws`; + } else { + const proto = location.protocol === "https:" ? "wss:" : "ws:"; + wsUrl = `${proto}//${location.host}/ws`; + } + const ws = new WebSocket(wsUrl); + + ws.onmessage = (e) => { + try { + cbRef.current(JSON.parse(e.data)); + } catch { /* ignore bad json */ } + }; + + ws.onclose = () => { + setTimeout(connect, 3000); + }; + + wsRef.current = ws; + }, [remoteBaseUrl]); + + useEffect(() => { + connect(); + return () => wsRef.current?.close(); + }, [connect]); +} diff --git a/memory-viewer/src/hooks/useZoom.ts b/memory-viewer/src/hooks/useZoom.ts new file mode 100644 index 0000000..aa2c14c --- /dev/null +++ b/memory-viewer/src/hooks/useZoom.ts @@ -0,0 +1,18 @@ +import { useState, useEffect } from "react"; + +const ZOOM_KEY = "memory-viewer-zoom"; +const ZOOM_LEVELS = [75, 80, 90, 100, 110, 125, 150]; +const DEFAULT_ZOOM = 100; + +export function useZoom() { + const [zoom, setZoom] = useState(() => { + const saved = localStorage.getItem(ZOOM_KEY); + return saved ? Number(saved) : DEFAULT_ZOOM; + }); + + useEffect(() => { + localStorage.setItem(ZOOM_KEY, String(zoom)); + }, [zoom]); + + return { zoom, setZoom, ZOOM_LEVELS }; +} diff --git a/memory-viewer/src/i18n.ts b/memory-viewer/src/i18n.ts new file mode 100644 index 0000000..12a6b6b --- /dev/null +++ b/memory-viewer/src/i18n.ts @@ -0,0 +1,244 @@ +export type Locale = "en" | "zh"; + +const dict: Record> = { + // Sidebar + "sidebar.agentConfig": { en: "Agent Config", zh: "智能体配置" }, + "sidebar.coreFiles": { en: "Core Files", zh: "核心文件" }, + "sidebar.dailyNotes": { en: "Daily Notes", zh: "每日日记" }, + "sidebar.today": { en: "Today", zh: "今日日记" }, + "sidebar.quickAccess": { en: "Quick Access", zh: "快速访问" }, + + // Skills Page + "skills.title": { en: "Skills", zh: "技能" }, + "skills.description": { en: "Agent capabilities and extensions", zh: "智能体能力与扩展" }, + "skills.empty": { en: "No skills found", zh: "未找到技能" }, + "sidebar.skills": { en: "Skills", zh: "技能" }, + "sidebar.files": { en: "Files", zh: "文件" }, + "sidebar.search": { en: "Search…", zh: "搜索…" }, + "sidebar.footer": { en: "Silicon Dawn · Memory Viewer", zh: "Silicon Dawn · 记忆查看器" }, + "sidebar.showSensitive": { en: "Show sensitive content", zh: "显示敏感内容" }, + "sidebar.hideSensitive": { en: "Hide sensitive content", zh: "隐藏敏感内容" }, + "sidebar.lightMode": { en: "Switch to light mode", zh: "切换到浅色模式" }, + "sidebar.darkMode": { en: "Switch to dark mode", zh: "切换到深色模式" }, + + // Agent Status + "agent.title": { en: "Agent Status", zh: "智能体状态" }, + "agent.gateway": { en: "Gateway", zh: "网关" }, + "agent.models": { en: "Models", zh: "模型" }, + "agent.heartbeat": { en: "Heartbeat", zh: "心跳" }, + "agent.config": { en: "Safe Config", zh: "安全配置" }, + "agent.running": { en: "Running", zh: "运行中" }, + "agent.stopped": { en: "Stopped", zh: "已停止" }, + "agent.uptime": { en: "uptime", zh: "运行时间" }, + "agent.port": { en: "Port", zh: "端口" }, + "agent.pid": { en: "PID", zh: "PID" }, + "agent.mode": { en: "Mode", zh: "模式" }, + "agent.primary": { en: "Primary Model", zh: "主模型" }, + "agent.ready": { en: "Ready to inference", zh: "就绪" }, + "agent.lastActivity": { en: "Last activity", zh: "最近活动" }, + "agent.noHeartbeat": { en: "No heartbeat data", zh: "无心跳数据" }, + "agent.channel": { en: "channel", zh: "通道" }, + "agent.loading": { en: "Loading status...", zh: "加载状态..." }, + "agent.error": { en: "Failed to load status. Ensure the server is running on the same machine as the agent.", zh: "加载失败。请确保服务器与 Agent 运行在同一台机器上。" }, + + // Dashboard + "dashboard.title": { en: "Dashboard", zh: "仪表盘" }, + "dashboard.uptime": { en: "Uptime", zh: "运行时间" }, + "dashboard.memory": { en: "Memory", zh: "内存" }, + "dashboard.load": { en: "Load", zh: "负载" }, + "dashboard.files": { en: "Files", zh: "文件" }, + "dashboard.mdTracked": { en: ".md files tracked", zh: "个 .md 文件" }, + "dashboard.todayMemory": { en: "Today's Memory", zh: "今日记忆" }, + "dashboard.viewFull": { en: "View full →", zh: "查看全部 →" }, + "dashboard.noMemoryToday": { en: "No memory entries for today yet.", zh: "今天还没有记忆条目。" }, + "dashboard.recentlyModified": { en: "Recently Modified", zh: "最近修改" }, + "dashboard.memoryByMonth": { en: "Memory by Month", zh: "月度记忆" }, + "dashboard.quickAccess": { en: "Quick Access", zh: "快速访问" }, + "dashboard.loading": { en: "Loading…", zh: "加载中…" }, + "dashboard.noFiles": { en: "No files found.", zh: "未找到文件。" }, + "dashboard.noMemoryFiles": { en: "No memory files found.", zh: "未找到记忆文件。" }, + "dashboard.characters": { en: "characters", zh: "个字符" }, + "dashboard.justNow": { en: "just now", zh: "刚刚" }, + "dashboard.minAgo": { en: "min ago", zh: "分钟前" }, + "dashboard.hAgo": { en: "h ago", zh: "小时前" }, + "dashboard.dAgo": { en: "d ago", zh: "天前" }, + + // FileViewer + "file.edit": { en: "Edit", zh: "编辑" }, + "file.save": { en: "Save", zh: "保存" }, + "file.cancel": { en: "Cancel", zh: "取消" }, + "file.saved": { en: "Saved", zh: "已保存" }, + "file.failedToLoad": { en: "Failed to load", zh: "加载失败" }, + "file.saveFailed": { en: "Save failed", zh: "保存失败" }, + "file.words": { en: "words", zh: "词" }, + "file.discardChanges": { en: "Discard unsaved changes?", zh: "放弃未保存的更改?" }, + "file.reloaded": { en: "Reloaded from server", zh: "已从服务器重新加载" }, + "changelog.title": { en: "Changelog", zh: "更新日志" }, + "changelog.back": { en: "Back", zh: "返回" }, + "sidebar.changelog": { en: "Changelog", zh: "更新日志" }, + "file.conflictTitle": { en: "File Conflict", zh: "文件冲突" }, + "file.conflictDesc": { en: "This file was modified on disk while you were editing. Your changes have not been saved.", zh: "文件在编辑期间被其他程序修改,您的更改尚未保存。" }, + "file.conflictTime": { en: "Modified at", zh: "修改时间" }, + "file.conflictOverwrite": { en: "Overwrite", zh: "覆盖" }, + "file.conflictReload": { en: "Reload", zh: "重新加载" }, + "file.loading": { en: "Loading…", zh: "加载中…" }, + "file.backToTop": { en: "Back to top", zh: "返回顶部" }, + "file.copy": { en: "Copy", zh: "复制" }, + + // Search + "search.placeholder": { en: "Search all memory files…", zh: "搜索所有记忆文件…" }, + "search.noResults": { en: "No results found for", zh: "未找到相关结果:" }, + "search.files": { en: "files", zh: "个文件" }, + "search.matches": { en: "matches", zh: "个匹配" }, + "search.results": { en: "results", zh: "个结果" }, + "settings.title": { en: "Settings", zh: "设置" }, + "settings.embedding.title": { en: "Vector Search", zh: "向量搜索" }, + "settings.embedding.description": { en: "Configure an OpenAI-compatible embedding API for semantic search. Supports OpenAI, DeepSeek, SiliconFlow, Ollama, etc.", zh: "配置 OpenAI 兼容的 Embedding API 进行语义搜索。支持 OpenAI、DeepSeek、SiliconFlow、Ollama 等。" }, + "settings.embedding.model": { en: "Model", zh: "模型" }, + "settings.testConnection": { en: "Test Connection", zh: "测试连接" }, + "settings.testing": { en: "Testing…", zh: "测试中…" }, + "settings.save": { en: "Save", zh: "保存" }, + "settings.saving": { en: "Saving…", zh: "保存中…" }, + "settings.saved": { en: "Saved", zh: "已保存" }, + "settings.saveFailed": { en: "Save failed", zh: "保存失败" }, + "search.modeText": { en: "Text", zh: "文本" }, + "search.modeSemantic": { en: "Semantic", zh: "语义" }, + "search.relevance": { en: "relevance", zh: "相关度" }, + + // Connections + "connections.title": { en: "Connections", zh: "连接管理" }, + "connections.add": { en: "Add", zh: "添加" }, + "connections.addNew": { en: "Add Connection", zh: "添加连接" }, + "connections.edit": { en: "Edit Connection", zh: "编辑连接" }, + "connections.save": { en: "Save", zh: "保存" }, + "connections.cancel": { en: "Cancel", zh: "取消" }, + "connections.refresh": { en: "Refresh", zh: "刷新" }, + "connections.active": { en: "Active", zh: "当前连接" }, + "connections.namePlaceholder": { en: "Name (e.g. Bot 01)", zh: "名称(如 Bot 01)" }, + "connections.urlPlaceholder": { en: "URL (e.g. http://host:8901)", zh: "URL(如 http://host:8901)" }, + "connections.tokenPlaceholder": { en: "Token (optional)", zh: "Token(可选)" }, + "connections.switchBot": { en: "Switch Bot", zh: "切换 Bot" }, + "connections.manage": { en: "Manage", zh: "管理" }, + "connections.modeDirect": { en: "Direct", zh: "直连" }, + "connections.modeGateway": { en: "Gateway", zh: "网关" }, + "connections.gatewayUrl": { en: "Gateway URL", zh: "网关地址" }, + "connections.gatewayUrlPlaceholder": { en: "Gateway URL (e.g. http://host:18789)", zh: "网关地址(如 http://host:18789)" }, + "connections.gatewayToken": { en: "Gateway Token", zh: "网关 Token" }, + "connections.gatewayTokenPlaceholder": { en: "Gateway API Token", zh: "网关 API Token" }, + "connections.mvPort": { en: "MV Port", zh: "MV 端口" }, + "connections.mvPortPlaceholder": { en: "Memory Viewer Port (default 8901)", zh: "Memory Viewer 端口(默认 8901)" }, + + // Bootstrap + "bootstrap.checking": { en: "Checking if Memory Viewer is running…", zh: "正在检测 Memory Viewer 是否运行…" }, + "bootstrap.alreadyRunning": { en: "Memory Viewer is already running! Connection saved.", zh: "Memory Viewer 已在运行!连接已保存。" }, + "bootstrap.notInstalled": { en: "Remote bot doesn't have Memory Viewer installed.", zh: "远程 Bot 未安装 Memory Viewer。" }, + "bootstrap.installPrompt": { en: "Would you like to install it via Gateway API? This will send commands to the remote bot.", zh: "是否通过网关 API 安装?这将向远程 Bot 发送安装指令。" }, + "bootstrap.install": { en: "Install", zh: "安装" }, + "bootstrap.installing": { en: "Installing Memory Viewer via Gateway…", zh: "正在通过网关安装 Memory Viewer…" }, + "bootstrap.botResponse": { en: "Bot Response", zh: "Bot 回复" }, + "bootstrap.verifying": { en: "Verifying installation…", zh: "正在验证安装…" }, + "bootstrap.success": { en: "Memory Viewer installed and connected successfully!", zh: "Memory Viewer 安装成功并已连接!" }, + "bootstrap.failed": { en: "Could not connect after installation. The bot may need more time.", zh: "安装后无法连接。Bot 可能需要更多时间。" }, + "bootstrap.retry": { en: "Retry Verification", zh: "重试验证" }, + "bootstrap.close": { en: "Close", zh: "关闭" }, + "bootstrap.error": { en: "Error", zh: "错误" }, + "bootstrap.done": { en: "Done", zh: "完成" }, + + // Heatmap + "heatmap.activeDays": { en: "active days", zh: "个活跃日" }, + "heatmap.total": { en: "total", zh: "总计" }, + "heatmap.perDay": { en: "/day", zh: "/天" }, + "heatmap.less": { en: "Less", zh: "少" }, + "heatmap.more": { en: "More", zh: "多" }, + "heatmap.noData": { en: "No data", zh: "无数据" }, + + // Timeline + "sidebar.timeline": { en: "Timeline", zh: "时间线" }, + "timeline.title": { en: "Memory Timeline", zh: "记忆时间线" }, + "timeline.loading": { en: "Loading memories…", zh: "正在加载记忆…" }, + "timeline.entries": { en: "entries", zh: "篇日记" }, + "timeline.words": { en: "chars", zh: "字符" }, + "timeline.tags": { en: "tags", zh: "标签" }, + "timeline.filterByTag": { en: "Filter by tag:", zh: "按标签筛选:" }, + "timeline.noResults": { en: "No entries found with this tag", zh: "未找到包含此标签的日记" }, + "timeline.empty": { en: "No memory entries found", zh: "未找到记忆日记" }, + "timeline.chars": { en: "chars", zh: "字" }, + "timeline.months": { en: "months", zh: "个月" }, + + // Tags + "sidebar.tags": { en: "Tags", zh: "标签" }, + "tags.title": { en: "Tags", zh: "标签" }, + "tags.subtitle": { en: "Browse memories by topic", zh: "按主题浏览记忆" }, + "tags.totalTags": { en: "tags", zh: "标签" }, + "tags.totalFiles": { en: "files", zh: "文件" }, + "tags.avgPerTag": { en: "avg", zh: "平均" }, + "tags.popular": { en: "Popular Tags", zh: "热门标签" }, + "tags.clear": { en: "Clear filter", zh: "清除筛选" }, + "tags.showing": { en: "Showing tag:", zh: "显示标签:" }, + "tags.files": { en: "files", zh: "个文件" }, + "tags.tags": { en: "tags", zh: "个标签" }, + "tags.noFiles": { en: "No files found with this tag", zh: "没有找到相关文件" }, + "tags.empty": { en: "No tags yet", zh: "暂无标签" }, + "tags.emptyHint": { en: "Use ## headers or #hashtags in Markdown to create tags", zh: "在 Markdown 中使用 ## 标题 或 #标签 来创建标签" }, + + // Settings + "settings.title": { en: "Settings", zh: "设置" }, + "sidebar.settings": { en: "Settings", zh: "设置" }, + "settings.embedding.title": { en: "Embedding Search", zh: "向量搜索" }, + "settings.embedding.enable": { en: "Enable Embedding Search", zh: "启用向量搜索" }, + "settings.embedding.enableDesc": { en: "Use OpenAI Embeddings API for semantic search", zh: "使用 OpenAI Embeddings API 进行语义搜索" }, + "settings.embedding.apiUrl": { en: "API URL", zh: "API 地址" }, + "settings.embedding.apiUrlDesc": { en: "OpenAI Embeddings API or compatible endpoint", zh: "OpenAI Embeddings API 或兼容的端点" }, + "settings.embedding.apiKey": { en: "API Key", zh: "API 密钥" }, + "settings.embedding.apiKeyDesc": { en: "OpenAI API key (sk-...)", zh: "OpenAI API 密钥 (sk-...)" }, + "settings.embedding.apiKeySet": { en: "API key is set", zh: "API 密钥已设置" }, + "settings.embedding.model": { en: "Model", zh: "模型" }, + "settings.embedding.modelDesc": { en: "Embedding model name, e.g. text-embedding-3-small", zh: "Embedding 模型名称,如 text-embedding-3-small" }, + "settings.embedding.test": { en: "Test Connection", zh: "测试连接" }, + "settings.embedding.testing": { en: "Testing...", zh: "测试中..." }, + "settings.save": { en: "Save Settings", zh: "保存设置" }, + "settings.saving": { en: "Saving...", zh: "保存中..." }, + + // Cron + "sidebar.cron": { en: "Cron Jobs", zh: "定时任务" }, + "cron.title": { en: "Cron Jobs", zh: "定时任务" }, + "cron.subtitle": { en: "Manage scheduled tasks", zh: "管理定时任务" }, + "cron.total": { en: "Total", zh: "总计" }, + "cron.enabled": { en: "Enabled", zh: "已启用" }, + "cron.disabled": { en: "Disabled", zh: "已禁用" }, + "cron.refresh": { en: "Refresh", zh: "刷新" }, + "cron.enable": { en: "Enable", zh: "启用" }, + "cron.disable": { en: "Disable", zh: "禁用" }, + "cron.runNow": { en: "Run Now", zh: "立即运行" }, + "cron.history": { en: "History", zh: "历史" }, + "cron.runHistory": { en: "Run History", zh: "运行历史" }, + "cron.loading": { en: "Loading…", zh: "加载中…" }, + "cron.noRuns": { en: "No run history", zh: "暂无运行记录" }, + "cron.empty": { en: "No cron jobs configured", zh: "暂无定时任务" }, + "cron.justNow": { en: "just now", zh: "刚刚" }, + "cron.soonLabel": { en: "soon", zh: "即将" }, + "cron.minAgo": { en: "m ago", zh: "分钟前" }, + "cron.minLater": { en: "m later", zh: "分钟后" }, + "cron.hAgo": { en: "h ago", zh: "小时前" }, + "cron.hLater": { en: "h later", zh: "小时后" }, + "cron.dAgo": { en: "d ago", zh: "天前" }, + "cron.dLater": { en: "d later", zh: "天后" }, + + // Months + "month.01": { en: "Jan", zh: "1月" }, + "month.02": { en: "Feb", zh: "2月" }, + "month.03": { en: "Mar", zh: "3月" }, + "month.04": { en: "Apr", zh: "4月" }, + "month.05": { en: "May", zh: "5月" }, + "month.06": { en: "Jun", zh: "6月" }, + "month.07": { en: "Jul", zh: "7月" }, + "month.08": { en: "Aug", zh: "8月" }, + "month.09": { en: "Sep", zh: "9月" }, + "month.10": { en: "Oct", zh: "10月" }, + "month.11": { en: "Nov", zh: "11月" }, + "month.12": { en: "Dec", zh: "12月" }, +}; + +export function translate(key: string, locale: Locale): string { + return dict[key]?.[locale] ?? key; +} diff --git a/memory-viewer/src/index.css b/memory-viewer/src/index.css new file mode 100644 index 0000000..947abde --- /dev/null +++ b/memory-viewer/src/index.css @@ -0,0 +1,444 @@ +@import "tailwindcss"; + +@theme { + --font-mono: 'JetBrains Mono', 'Fira Code', monospace; +} + +/* ===== Theme System ===== */ +/* Light default: Maple (warm, soft, nature-inspired) */ +:root, .theme-maple-light { + --bg-primary: #faf8f5; + --bg-secondary: #ffffff; + --bg-tertiary: #f5f0eb; + --bg-hover: rgba(139,90,43,0.04); + --bg-active: rgba(180,83,9,0.08); + --border: #e8ddd0; + --border-light: #f0e8de; + --text-primary: #1c1410; + --text-secondary: #44362a; + --text-muted: #7c6a58; + --text-faint: #a89580; + --code-bg: #f5efe8; + --code-text: #9a3412; + --pre-bg: #faf6f1; + --pre-border: #e8ddd0; + --pre-text: #44362a; + --link: #b45309; + --link-hover: #92400e; + --blockquote-border: rgba(180,83,9,0.35); + --blockquote-text: #7c6a58; + --blockquote-bg: rgba(245,240,235,0.6); + --th-bg: #f5efe8; + --tr-hover: rgba(139,90,43,0.04); + --scrollbar-thumb: rgba(139,90,43,0.2); + --scrollbar-hover: rgba(139,90,43,0.35); + --toast-bg: #292017; + --toast-text: #fff; + --editor-bg: rgba(245,240,235,0.5); + --ring: rgba(180,83,9,0.4); + /* Markdown headings */ + --md-h1: #78350f; + --md-h2: #92400e; + --md-h3: #a16207; + --md-h1-border: #d6c4a8; + --md-h2-border: #e8ddd0; + --md-strong: #1c1410; + --md-em: #92400e; + --md-hr: #d6c4a8; +} + +/* Dark default: Material (Google Material Design inspired) */ +.dark, .theme-material-dark { + --bg-primary: #0f111a; + --bg-secondary: #1a1c2b; + --bg-tertiary: #242637; + --bg-hover: rgba(130,170,255,0.04); + --bg-active: rgba(130,170,255,0.12); + --border: #2a2d3e; + --border-light: rgba(42,45,62,0.6); + --text-primary: #eeffff; + --text-secondary: #b0bec5; + --text-muted: #78909c; + --text-faint: #546e7a; + --code-bg: #1d1f31; + --code-text: #c3e88d; + --pre-bg: #0f111a; + --pre-border: #2a2d3e; + --pre-text: #b0bec5; + --link: #82aaff; + --link-hover: #a6c4ff; + --blockquote-border: rgba(130,170,255,0.4); + --blockquote-text: #78909c; + --blockquote-bg: rgba(36,38,55,0.5); + --th-bg: rgba(36,38,55,0.6); + --tr-hover: rgba(15,17,26,0.5); + --scrollbar-thumb: rgba(84,110,122,0.5); + --scrollbar-hover: rgba(84,110,122,0.7); + --toast-bg: #242637; + --toast-text: #eeffff; + --editor-bg: rgba(15,17,26,0.6); + --ring: rgba(130,170,255,0.5); + /* Markdown headings */ + --md-h1: #c792ea; + --md-h2: #82aaff; + --md-h3: #ffcb6b; + --md-h1-border: #3a3d52; + --md-h2-border: rgba(42,45,62,0.6); + --md-strong: #eeffff; + --md-em: #f78c6c; + --md-hr: #3a3d52; +} + +body { + background-color: var(--bg-primary); + color: var(--text-primary); + font-family: 'Inter', 'Noto Sans SC', -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif; + -webkit-font-smoothing: antialiased; + font-size: 16px; +} + +/* Tesla mode — large touch-friendly UI for car displays */ +.tesla-mode { + font-size: 24px; +} +.tesla-mode .markdown-body { font-size: 24px !important; line-height: 1.9; } +.tesla-mode .markdown-body h1 { font-size: 2.4rem; } +.tesla-mode .markdown-body h2 { font-size: 2rem; } +.tesla-mode .markdown-body h3 { font-size: 1.6rem; } +.tesla-mode .markdown-body h4 { font-size: 1.3rem; } +.tesla-mode .markdown-body code { font-size: 0.85em; padding: 4px 8px; } +.tesla-mode .markdown-body pre { padding: 20px; font-size: 0.85em; } +.tesla-mode .markdown-body li { margin-bottom: 6px; } +.tesla-mode .sidebar-header { padding: 1.2rem 1.5rem; } +.tesla-mode .sidebar-header button { font-size: 1.3rem !important; } +.tesla-mode .sidebar-header svg { width: 24px !important; height: 24px !important; } +.tesla-mode .sidebar-footer { font-size: 1rem; padding: 1rem 1.5rem; } +.tesla-mode .sidebar button, +.tesla-mode .sidebar a { min-height: 56px; padding: 12px 16px !important; font-size: 1.15rem; } +.tesla-mode .btn-primary, .tesla-mode .btn-secondary { min-height: 56px; padding: 14px 24px !important; font-size: 1.15rem; } +.tesla-mode button svg { min-width: 24px; min-height: 24px; } +.tesla-mode button { min-height: 48px; min-width: 48px; } +.tesla-mode input[type="text"], .tesla-mode input[type="search"] { min-height: 56px; font-size: 1.15rem; padding: 14px 18px; } +.tesla-mode nav a, .tesla-mode nav button, .tesla-mode [role="tab"] { min-height: 56px; padding: 12px 20px !important; font-size: 1.15rem; } + +/* Buttons */ +.btn-primary { + @apply px-3.5 py-1.5 bg-blue-600 hover:bg-blue-500 text-white font-medium rounded-lg transition-colors; +} +.btn-secondary { + padding: 6px 14px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + color: var(--text-secondary); + border-radius: 8px; + transition: all 0.15s; +} +.btn-secondary:hover { + background: var(--bg-hover); + border-color: var(--text-faint); +} + +/* Sidebar */ +.sidebar { + background: var(--bg-secondary); + border-color: var(--border); +} +.sidebar-header { + border-color: var(--border); +} +.sidebar-footer { + border-color: var(--border); + color: var(--text-faint); +} +.sidebar-section-title { + color: var(--text-faint); +} +.sidebar-item { + color: var(--text-muted); + transition: all 0.15s; +} +.sidebar-item:hover { + background: var(--bg-hover); + color: var(--text-secondary); +} +.sidebar-item-active { + background: var(--bg-active); + color: var(--link); +} +.search-trigger { + background: var(--bg-hover); + border: 1px solid var(--border-light); + color: var(--text-faint); +} +.search-trigger:hover { + color: var(--text-muted); + border-color: var(--text-faint); +} + +/* Markdown — optimized for readability */ +.markdown-body { + color: var(--text-secondary); + line-height: 1.85; + letter-spacing: 0.01em; + font-size: 15px; +} +.markdown-body h1 { + @apply text-2xl font-bold pb-3 mb-5 mt-10 first:mt-0; + color: var(--md-h1, var(--text-primary)); + border-bottom: 2px solid var(--md-h1-border, var(--border)); + line-height: 1.3; +} +.markdown-body h2 { + @apply text-xl font-semibold pb-2 mb-4 mt-8; + color: var(--md-h2, var(--text-primary)); + border-bottom: 1px solid var(--md-h2-border, var(--border-light)); + line-height: 1.35; +} +.markdown-body h3 { + @apply text-lg font-semibold mb-3 mt-6; + color: var(--md-h3, var(--text-primary)); + line-height: 1.4; +} +.markdown-body h4 { + @apply text-base font-semibold mb-2 mt-5; + color: var(--text-secondary); + line-height: 1.4; +} +.markdown-body p { + @apply mb-4; + line-height: 1.85; +} +.markdown-body ul { @apply list-disc pl-6 mb-4 space-y-1.5; } +.markdown-body ol { @apply list-decimal pl-6 mb-4 space-y-1.5; } +.markdown-body li { + color: var(--text-secondary); + line-height: 1.75; +} +.markdown-body li > ul, +.markdown-body li > ol { + @apply mt-1.5 mb-0; +} +.markdown-body code { + background: var(--code-bg); + color: var(--code-text); + @apply px-1.5 py-0.5 rounded text-[0.85em] font-mono; + font-weight: 500; +} +.markdown-body pre { + background: var(--pre-bg); + border: 1px solid var(--pre-border); + font-family: var(--font-mono); + @apply rounded-xl p-4 mb-5 overflow-x-auto text-sm; +} +.markdown-body pre code { background: transparent; padding: 0; color: var(--pre-text); font-weight: normal; font-family: inherit; } +.markdown-body pre code span { background: transparent !important; } +.markdown-body code[class*="language-"] { background: transparent !important; } +.markdown-body blockquote { + border-left: 4px solid var(--blockquote-border); + padding: 0.75rem 1rem; + color: var(--blockquote-text); + background: var(--blockquote-bg, var(--bg-hover)); + border-radius: 0 0.5rem 0.5rem 0; + @apply italic my-5; +} +.markdown-body blockquote p:last-child { @apply mb-0; } +.markdown-body a { + color: var(--link); + text-decoration: underline; + text-decoration-color: rgba(59,130,246,0.3); + text-underline-offset: 3px; + transition: all 0.15s; +} +.markdown-body a:hover { color: var(--link-hover); text-decoration-color: rgba(59,130,246,0.6); } +.markdown-body table { @apply w-full border-collapse mb-5 text-sm; } +.markdown-body th { + background: var(--th-bg); + border: 1px solid var(--border); + @apply px-4 py-2.5 text-left font-semibold; + color: var(--text-secondary); +} +.markdown-body td { + border: 1px solid var(--border); + @apply px-4 py-2.5; + color: var(--text-secondary); + line-height: 1.65; +} +.markdown-body tr:hover td { background: var(--tr-hover); } +.markdown-body hr { border-color: var(--md-hr, var(--border)); @apply my-8; } +.markdown-body strong { color: var(--md-strong, var(--text-primary)); @apply font-semibold; } +.markdown-body em { color: var(--md-em, var(--text-muted)); } +.markdown-body img { @apply rounded-lg max-w-full my-2; } +/* Checkbox lists */ +.markdown-body input[type="checkbox"] { + @apply mr-2 rounded; + accent-color: #3b82f6; +} + +/* Editor */ +.editor-textarea { + background: var(--editor-bg); + color: var(--text-primary); + border: 1px solid var(--border); + @apply w-full h-full font-mono text-sm p-4 rounded-lg resize-none; + tab-size: 2; +} +.editor-textarea:focus { + outline: none; + box-shadow: 0 0 0 2px var(--ring); + border-color: var(--ring); +} + +/* Toast */ +.toast { + position: fixed; + bottom: 1.5rem; + right: 1.5rem; + background: var(--toast-bg); + color: var(--toast-text); + @apply px-4 py-2.5 rounded-xl shadow-2xl z-50 text-sm; + border: 1px solid var(--border); + animation: toast-in 0.3s ease-out; +} +@keyframes toast-in { + from { transform: translateY(16px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } +} + +/* Sensitive content blur (Telegram-style) */ +.sensitive-blur { + filter: blur(6px); + -webkit-filter: blur(6px); + user-select: none; + transition: filter 0.3s ease; + cursor: default; + border-radius: 4px; + padding: 0 2px; +} +.sensitive-blur:hover { + filter: blur(4px); +} +.sensitive-revealed .sensitive-blur { + filter: none !important; + -webkit-filter: none !important; + user-select: auto; + cursor: text; +} + +/* Scroll to top button */ +.scroll-top-btn { + position: fixed; + bottom: 1.5rem; + right: 1.5rem; + width: 40px; + height: 40px; + border-radius: 50%; + background: var(--bg-tertiary); + border: 1px solid var(--border); + color: var(--text-muted); + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + z-index: 40; + transition: all 0.2s; + box-shadow: 0 2px 8px rgba(0,0,0,0.15); + animation: toast-in 0.2s ease-out; +} +.scroll-top-btn:hover { + background: var(--bg-active); + color: var(--link); +} + +/* Code block copy button */ +.markdown-body pre { + position: relative; +} +.code-copy-btn { + position: absolute; + top: 8px; + right: 8px; + padding: 4px 6px; + border-radius: 6px; + background: var(--bg-tertiary); + border: 1px solid var(--border); + color: var(--text-muted); + cursor: pointer; + opacity: 0; + transition: opacity 0.15s, background 0.15s; + z-index: 5; + display: flex; + align-items: center; +} +.markdown-body pre:hover .code-copy-btn { + opacity: 1; +} +.code-copy-btn:hover { + background: var(--bg-active); + color: var(--link); +} + +/* Shiki code blocks */ +.shiki-wrapper pre { + margin: 0; + border-radius: 0.75rem; + font-size: 0.85rem; + line-height: 1.7; + border: 1px solid var(--pre-border); + padding: 1rem; + overflow-x: auto; + white-space: pre; + font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; +} +.shiki-wrapper pre code { + font-family: inherit; + background: transparent; + tab-size: 2; + counter-reset: line; +} +.shiki-wrapper pre code .line { + display: inline-block; + width: 100%; +} +.shiki-wrapper pre code .line::before { + counter-increment: line; + content: counter(line); + display: inline-block; + width: 2.5em; + margin-right: 1em; + text-align: right; + color: var(--text-faint); + opacity: 0.5; + font-size: 0.8rem; + user-select: none; + -webkit-user-select: none; +} + +/* Shiki dual theme support */ +html.dark .shiki, +html.dark .shiki span { + color: var(--shiki-dark) !important; + background-color: transparent !important; +} +html:not(.dark) .shiki, +html:not(.dark) .shiki span { + color: var(--shiki-light) !important; + background-color: transparent !important; +} + +/* Scrollbar */ +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: var(--scrollbar-thumb); border-radius: 9999px; } +::-webkit-scrollbar-thumb:hover { background: var(--scrollbar-hover); } + +/* Superscript and subscript - use monospace for alignment */ +.markdown-body sup, +.markdown-body sub { + font-family: 'JetBrains Mono', 'Fira Code', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace; + font-size: 0.65em; + vertical-align: super; + line-height: 0; +} +.markdown-body sub { + vertical-align: sub; +} diff --git a/memory-viewer/src/main.tsx b/memory-viewer/src/main.tsx new file mode 100644 index 0000000..c2d5180 --- /dev/null +++ b/memory-viewer/src/main.tsx @@ -0,0 +1,18 @@ +import React, { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import "./index.css"; +import "./themes"; +import App from "./App"; +import { pluginRegistry } from "./plugins/registry"; + +// Expose React for external plugins +(window as any).__MV_REACT__ = React; + +// Load external plugins (non-blocking) +pluginRegistry.loadExternal(); + +createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/memory-viewer/src/plugins/PluginSlot.tsx b/memory-viewer/src/plugins/PluginSlot.tsx new file mode 100644 index 0000000..aaf3f3c --- /dev/null +++ b/memory-viewer/src/plugins/PluginSlot.tsx @@ -0,0 +1,25 @@ +import { useSyncExternalStore, RefObject } from "react"; +import { pluginRegistry } from "./registry"; +import { PluginSlotName } from "./types"; + +interface PluginSlotProps { + name: PluginSlotName; + filePath: string; + content: string; + renderedRef: RefObject; +} + +export function PluginSlot({ name, filePath, content, renderedRef }: PluginSlotProps) { + const version = useSyncExternalStore(pluginRegistry.subscribe, pluginRegistry.getSnapshot); + const components = pluginRegistry.getPluginsForSlot(name, filePath); + + if (components.length === 0) return null; + + return ( + <> + {components.map((Component, i) => ( + + ))} + + ); +} diff --git a/memory-viewer/src/plugins/registry.ts b/memory-viewer/src/plugins/registry.ts new file mode 100644 index 0000000..f257ed2 --- /dev/null +++ b/memory-viewer/src/plugins/registry.ts @@ -0,0 +1,116 @@ +import { ComponentType } from "react"; +import { MemoryViewerPlugin, PluginSlotName, SlotProps } from "./types"; +import { getBaseUrl } from "../api"; + +const DISABLED_KEY = "mv-plugins-disabled"; + +class PluginRegistry { + private plugins: Map = new Map(); + private disabled: Set; + private listeners: Set<() => void> = new Set(); + private version = 0; + + constructor() { + try { + const raw = localStorage.getItem(DISABLED_KEY); + this.disabled = new Set(raw ? JSON.parse(raw) : []); + } catch { + this.disabled = new Set(); + } + } + + private notify() { + this.version++; + this.listeners.forEach((fn) => fn()); + } + + private persistDisabled() { + localStorage.setItem(DISABLED_KEY, JSON.stringify([...this.disabled])); + } + + register(plugin: MemoryViewerPlugin) { + this.plugins.set(plugin.id, plugin); + if (!this.disabled.has(plugin.id)) { + plugin.onActivate?.(); + } + this.notify(); + } + + unregister(id: string) { + const plugin = this.plugins.get(id); + if (plugin) { + plugin.onDeactivate?.(); + this.plugins.delete(id); + this.notify(); + } + } + + enable(id: string) { + this.disabled.delete(id); + this.persistDisabled(); + this.plugins.get(id)?.onActivate?.(); + this.notify(); + } + + disable(id: string) { + this.disabled.add(id); + this.persistDisabled(); + this.plugins.get(id)?.onDeactivate?.(); + this.notify(); + } + + isEnabled(id: string): boolean { + return !this.disabled.has(id); + } + + getAll(): MemoryViewerPlugin[] { + return [...this.plugins.values()]; + } + + getPluginsForSlot(slotName: PluginSlotName, filePath: string): ComponentType[] { + const result: ComponentType[] = []; + for (const plugin of this.plugins.values()) { + if (this.disabled.has(plugin.id)) continue; + const component = plugin.slots?.[slotName]; + if (!component) continue; + if (plugin.fileFilter && !plugin.fileFilter(filePath)) continue; + result.push(component); + } + return result; + } + + /** Load external plugins from server */ + async loadExternal() { + try { + const res = await fetch(`${getBaseUrl()}/api/plugins`); + const list: { id: string; name: string; entry: string }[] = await res.json(); + for (const info of list) { + if (this.plugins.has(info.id)) continue; + try { + const url = `${getBaseUrl()}/api/plugins/${info.id}/${info.entry}`; + const mod = await import(/* @vite-ignore */ url); + const plugin: MemoryViewerPlugin = mod.default || mod.plugin; + if (plugin?.id) { + this.register(plugin); + } + } catch (e) { + console.warn(`Failed to load external plugin "${info.id}":`, e); + } + } + } catch { + // No external plugins available + } + } + + // useSyncExternalStore compatible + subscribe = (listener: () => void): (() => void) => { + this.listeners.add(listener); + return () => this.listeners.delete(listener); + }; + + getSnapshot = (): number => { + return this.version; + }; +} + +export const pluginRegistry = new PluginRegistry(); diff --git a/memory-viewer/src/plugins/types.ts b/memory-viewer/src/plugins/types.ts new file mode 100644 index 0000000..1d78a3f --- /dev/null +++ b/memory-viewer/src/plugins/types.ts @@ -0,0 +1,20 @@ +import { RefObject, ComponentType } from "react"; + +export interface SlotProps { + filePath: string; + content: string; + renderedRef: RefObject; +} + +export type PluginSlotName = 'fileviewer-toolbar' | 'fileviewer-footer' | 'sidebar-bottom'; + +export interface MemoryViewerPlugin { + id: string; + name: string; + version: string; + description?: string; + onActivate?: () => void; + onDeactivate?: () => void; + slots?: Partial>>; + fileFilter?: (filePath: string) => boolean; +} diff --git a/memory-viewer/src/test/setup.ts b/memory-viewer/src/test/setup.ts new file mode 100644 index 0000000..a0c9d16 --- /dev/null +++ b/memory-viewer/src/test/setup.ts @@ -0,0 +1,8 @@ +import '@testing-library/jest-dom'; +import { cleanup } from '@testing-library/react'; +import { afterEach } from 'vitest'; + +// Runs a cleanup after each test case (e.g. clearing jsdom) +afterEach(() => { + cleanup(); +}); diff --git a/memory-viewer/src/themes/apply.ts b/memory-viewer/src/themes/apply.ts new file mode 100644 index 0000000..a6040ed --- /dev/null +++ b/memory-viewer/src/themes/apply.ts @@ -0,0 +1,84 @@ +import type { MarkdownTheme, ThemeStyles } from "./types"; + +const SELECTOR_MAP: Record = { + h1: "h1", + h2: "h2", + h3: "h3", + h4: "h4", + p: "p", + a: "a", + strong: "strong", + em: "em", + blockquote: "blockquote", + code: "code", + pre: "pre", + precode: "pre code", + ul: "ul", + ol: "ol", + li: "li", + table: "table", + th: "th", + td: "td", + hr: "hr", + img: "img", +}; + +function applyInlineStyle(el: HTMLElement, styleStr: string) { + el.setAttribute("style", styleStr); +} + +export function applyThemeStyles(article: HTMLElement, theme: MarkdownTheme) { + const isDark = document.documentElement.classList.contains("dark"); + const styles: ThemeStyles | undefined = (isDark && theme.darkStyles) ? theme.darkStyles : theme.styles; + if (!styles) return; + + // Apply body style to article + if (styles.body) { + article.setAttribute("style", styles.body); + } + + // Apply styles per selector + for (const [key, selector] of Object.entries(SELECTOR_MAP)) { + const styleStr = styles[key as keyof typeof styles]; + if (!styleStr || key === "precode") continue; + + // Skip shiki-generated elements: don't touch pre/code inside .shiki-wrapper + const elements = article.querySelectorAll(selector); + for (const el of elements) { + if (key === "code" && el.closest(".shiki-wrapper")) continue; + if (key === "pre" && el.closest(".shiki-wrapper")) continue; + applyInlineStyle(el as HTMLElement, styleStr); + } + } + + // Special: precode + if (styles.precode) { + const precodes = article.querySelectorAll("pre code"); + for (const el of precodes) { + if (el.closest(".shiki-wrapper")) continue; + applyInlineStyle(el as HTMLElement, styles.precode); + } + } + + // Special: hrContent - replace hr with styled text + if (styles.hrContent) { + const hrs = article.querySelectorAll("hr"); + for (const hr of hrs) { + const p = document.createElement("p"); + p.textContent = styles.hrContent; + if (styles.hr) applyInlineStyle(p, styles.hr); + hr.replaceWith(p); + } + } +} + +export function cleanInlineStyles(article: Element | null) { + if (!article) return; + (article as HTMLElement).removeAttribute("style"); + const all = article.querySelectorAll("*"); + for (const el of all) { + // Don't remove styles from shiki-generated spans + if (el.closest(".shiki-wrapper")) continue; + (el as HTMLElement).removeAttribute("style"); + } +} diff --git a/memory-viewer/src/themes/builtin/default.ts b/memory-viewer/src/themes/builtin/default.ts new file mode 100644 index 0000000..fa817a7 --- /dev/null +++ b/memory-viewer/src/themes/builtin/default.ts @@ -0,0 +1,6 @@ +import type { MarkdownTheme } from "../types"; + +export const defaultTheme: MarkdownTheme = { + id: "default", + name: "Default", +}; diff --git a/memory-viewer/src/themes/builtin/medium.ts b/memory-viewer/src/themes/builtin/medium.ts new file mode 100644 index 0000000..9b8ac83 --- /dev/null +++ b/memory-viewer/src/themes/builtin/medium.ts @@ -0,0 +1,52 @@ +import type { MarkdownTheme } from "../types"; + +export const mediumTheme: MarkdownTheme = { + id: "medium", + name: "Medium", + styles: { + body: 'font-family:Charter,"Bitstream Charter","Nimbus Roman No9 L",Georgia,"Times New Roman",serif;font-size:15px;line-height:1.75;color:rgba(36,36,36,1);letter-spacing:-0.003em;word-break:break-word;', + h1: 'font-size:24px;font-weight:700;color:rgba(36,36,36,1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;line-height:1.22;margin:1.8em 0 0.4em;letter-spacing:-0.016em;', + h2: 'font-size:20px;font-weight:700;color:rgba(36,36,36,1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;line-height:1.28;margin:1.6em 0 0.3em;letter-spacing:-0.012em;', + h3: 'font-size:17px;font-weight:700;color:rgba(36,36,36,1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;line-height:1.36;margin:1.4em 0 0.2em;', + h4: 'font-size:15px;font-weight:700;color:rgba(36,36,36,1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;margin:1.2em 0 0.15em;', + p: 'margin:0.8em 0;line-height:1.75;font-size:15px;', + a: 'color:inherit;text-decoration:underline;text-decoration-color:rgba(36,36,36,0.4);text-underline-offset:2px;', + strong: 'font-weight:700;', + em: 'font-style:italic;', + blockquote: 'border-left:3px solid rgba(36,36,36,1);padding:0 0 0 18px;margin:1.2em 0;color:rgba(36,36,36,0.8);font-style:italic;font-size:16px;line-height:1.75;', + code: 'background:rgba(0,0,0,0.05);padding:2px 5px;border-radius:3px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:13px;color:rgba(36,36,36,0.9);', + pre: 'background:#f2f2f2;padding:16px 20px;border-radius:4px;overflow-x:auto;margin:1.2em 0;', + precode: 'font-family:Menlo,Monaco,"Courier New",monospace;font-size:13px;line-height:1.7;color:rgba(36,36,36,0.9);background:none;padding:0;', + ul: 'padding-left:26px;margin:0.8em 0;list-style-type:disc;', + ol: 'padding-left:26px;margin:0.8em 0;', + li: 'margin:0.3em 0;line-height:1.75;font-size:15px;', + table: 'border-collapse:collapse;width:100%;margin:1.5em 0;font-size:14px;', + th: 'border:1px solid #d0d7de;padding:6px 13px;font-weight:600;text-align:left;background:#f6f8fa;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;', + td: 'border:1px solid #d0d7de;padding:6px 13px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;', + hr: 'border:none;text-align:center;margin:2.5em auto;width:100%;height:1px;background:rgba(36,36,36,0.15);', + img: 'max-width:100%;height:auto;', + }, + darkStyles: { + body: 'font-family:Charter,"Bitstream Charter","Nimbus Roman No9 L",Georgia,"Times New Roman",serif;font-size:15px;line-height:1.75;color:rgba(232,230,227,1);letter-spacing:-0.003em;word-break:break-word;', + h1: 'font-size:24px;font-weight:700;color:rgba(232,230,227,1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;line-height:1.22;margin:1.8em 0 0.4em;letter-spacing:-0.016em;', + h2: 'font-size:20px;font-weight:700;color:rgba(232,230,227,1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;line-height:1.28;margin:1.6em 0 0.3em;letter-spacing:-0.012em;', + h3: 'font-size:17px;font-weight:700;color:rgba(232,230,227,1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;line-height:1.36;margin:1.4em 0 0.2em;', + h4: 'font-size:15px;font-weight:700;color:rgba(232,230,227,1);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;margin:1.2em 0 0.15em;', + p: 'margin:0.8em 0;line-height:1.75;font-size:15px;color:rgba(232,230,227,0.9);', + a: 'color:rgba(232,230,227,0.9);text-decoration:underline;text-decoration-color:rgba(232,230,227,0.4);text-underline-offset:2px;', + strong: 'font-weight:700;color:rgba(232,230,227,1);', + em: 'font-style:italic;color:rgba(232,230,227,0.8);', + blockquote: 'border-left:3px solid rgba(232,230,227,0.6);padding:0 0 0 18px;margin:1.2em 0;color:rgba(232,230,227,0.7);font-style:italic;font-size:16px;line-height:1.75;', + code: 'background:rgba(255,255,255,0.08);padding:2px 5px;border-radius:3px;font-family:Menlo,Monaco,"Courier New",monospace;font-size:13px;color:rgba(232,230,227,0.9);', + pre: 'background:rgba(255,255,255,0.05);padding:16px 20px;border-radius:4px;overflow-x:auto;margin:1.2em 0;', + precode: 'font-family:Menlo,Monaco,"Courier New",monospace;font-size:13px;line-height:1.7;color:rgba(232,230,227,0.9);background:none;padding:0;', + ul: 'padding-left:26px;margin:0.8em 0;list-style-type:disc;color:rgba(232,230,227,0.9);', + ol: 'padding-left:26px;margin:0.8em 0;color:rgba(232,230,227,0.9);', + li: 'margin:0.3em 0;line-height:1.75;font-size:15px;color:rgba(232,230,227,0.9);', + table: 'border-collapse:collapse;width:100%;margin:1.5em 0;font-size:14px;', + th: 'border:1px solid #3a3d52;padding:6px 13px;font-weight:600;text-align:left;background:rgba(255,255,255,0.05);font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;color:rgba(232,230,227,1);', + td: 'border:1px solid #3a3d52;padding:6px 13px;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Helvetica,Arial,sans-serif;color:rgba(232,230,227,0.9);', + hr: 'border:none;text-align:center;margin:2.5em auto;width:100%;height:1px;background:rgba(232,230,227,0.15);', + img: 'max-width:100%;height:auto;', + }, +}; diff --git a/memory-viewer/src/themes/index.ts b/memory-viewer/src/themes/index.ts new file mode 100644 index 0000000..39a1f74 --- /dev/null +++ b/memory-viewer/src/themes/index.ts @@ -0,0 +1,9 @@ +import { themeRegistry, useMarkdownTheme } from "./registry"; +import { defaultTheme } from "./builtin/default"; +import { mediumTheme } from "./builtin/medium"; + +themeRegistry.register(defaultTheme); +themeRegistry.register(mediumTheme); + +export { themeRegistry, useMarkdownTheme }; +export type { MarkdownTheme } from "./types"; diff --git a/memory-viewer/src/themes/registry.ts b/memory-viewer/src/themes/registry.ts new file mode 100644 index 0000000..66f8a35 --- /dev/null +++ b/memory-viewer/src/themes/registry.ts @@ -0,0 +1,67 @@ +import { useSyncExternalStore, useCallback } from "react"; +import type { MarkdownTheme } from "./types"; + +const STORAGE_KEY = "mv-md-theme"; + +class ThemeRegistry { + private themes = new Map(); + private listeners = new Set<() => void>(); + private currentId: string; + private snapshot = { version: 0 }; + + constructor() { + this.currentId = localStorage.getItem(STORAGE_KEY) || "medium"; + } + + register(theme: MarkdownTheme) { + this.themes.set(theme.id, theme); + this.notify(); + } + + get(id: string): MarkdownTheme | undefined { + return this.themes.get(id); + } + + list(): MarkdownTheme[] { + return Array.from(this.themes.values()); + } + + getCurrent(): MarkdownTheme { + return this.themes.get(this.currentId) || this.themes.get("default") || { id: "default", name: "Default" }; + } + + setCurrent(id: string) { + if (this.currentId === id) return; + this.currentId = id; + localStorage.setItem(STORAGE_KEY, id); + this.notify(); + } + + private notify() { + this.snapshot = { version: this.snapshot.version + 1 }; + this.listeners.forEach((l) => l()); + } + + subscribe = (listener: () => void) => { + this.listeners.add(listener); + return () => { this.listeners.delete(listener); }; + }; + + getSnapshot = () => this.snapshot; +} + +export const themeRegistry = new ThemeRegistry(); + +export function useMarkdownTheme() { + useSyncExternalStore(themeRegistry.subscribe, themeRegistry.getSnapshot); + + const setTheme = useCallback((id: string) => { + themeRegistry.setCurrent(id); + }, []); + + return { + current: themeRegistry.getCurrent(), + setTheme, + themes: themeRegistry.list(), + }; +} diff --git a/memory-viewer/src/themes/types.ts b/memory-viewer/src/themes/types.ts new file mode 100644 index 0000000..7b554ad --- /dev/null +++ b/memory-viewer/src/themes/types.ts @@ -0,0 +1,33 @@ +export interface ThemeStyles { + body?: string; + h1?: string; + h2?: string; + h3?: string; + h4?: string; + p?: string; + a?: string; + strong?: string; + em?: string; + blockquote?: string; + code?: string; + pre?: string; + precode?: string; + ul?: string; + ol?: string; + li?: string; + table?: string; + th?: string; + td?: string; + hr?: string; + hrContent?: string; + img?: string; +} + +export interface MarkdownTheme { + id: string; + name: string; + /** Light mode styles (also used as default/only styles) */ + styles?: ThemeStyles; + /** Dark mode styles — if absent, falls back to styles */ + darkStyles?: ThemeStyles; +} diff --git a/memory-viewer/take-screenshots.mjs b/memory-viewer/take-screenshots.mjs new file mode 100644 index 0000000..eae4cc8 --- /dev/null +++ b/memory-viewer/take-screenshots.mjs @@ -0,0 +1,77 @@ +import { chromium } from 'playwright'; +import { join } from 'path'; + +const BASE = 'http://localhost:8901'; +const DOCS = join(import.meta.dirname, 'docs'); +const VP = { width: 1280, height: 800 }; + +async function screenshot(page, name) { + await page.waitForTimeout(500); + await page.screenshot({ path: join(DOCS, name), fullPage: false }); + console.log(`✅ ${name}`); +} + +const browser = await chromium.launch(); + +// --- DARK THEME --- +let ctx = await browser.newContext({ viewport: VP, colorScheme: 'dark' }); +let page = await ctx.newPage(); + +// Dashboard (dark) +await page.goto(BASE); +await page.waitForLoadState('networkidle'); +await screenshot(page, 'screenshot-dashboard-dark.png'); + +// Viewer - open MEMORY.md (dark) +// Click on MEMORY.md in file tree +await page.click('text=MEMORY.md'); +await page.waitForTimeout(800); +await screenshot(page, 'screenshot-viewer-dark.png'); + +// Editor mode (dark) - click edit button +const editBtn = page.locator('button:has-text("Edit"), button[title*="edit"], button[title*="Edit"], [aria-label*="edit"], [aria-label*="Edit"]').first(); +if (await editBtn.count() > 0) { + await editBtn.click(); + await page.waitForTimeout(500); + await screenshot(page, 'screenshot-editor-dark.png'); +} + +// Search panel (dark) - Ctrl+K +await page.keyboard.press('Control+k'); +await page.waitForTimeout(500); +// Type something to show results +await page.keyboard.type('memory'); +await page.waitForTimeout(800); +await screenshot(page, 'screenshot-search-dark.png'); +await page.keyboard.press('Escape'); + +await ctx.close(); + +// --- LIGHT THEME --- +ctx = await browser.newContext({ viewport: VP, colorScheme: 'light' }); +page = await ctx.newPage(); + +// Set light theme via localStorage before navigating +await page.addInitScript(() => { + localStorage.setItem('theme', 'light'); +}); +await page.goto(BASE); +await page.waitForLoadState('networkidle'); +// Also try setting it after load +await page.evaluate(() => { + localStorage.setItem('theme', 'light'); + document.documentElement.classList.remove('dark'); + document.documentElement.classList.add('light'); +}); +await page.reload(); +await page.waitForLoadState('networkidle'); +await screenshot(page, 'screenshot-dashboard-light.png'); + +// Viewer light +await page.click('text=MEMORY.md'); +await page.waitForTimeout(800); +await screenshot(page, 'screenshot-viewer-light.png'); + +await ctx.close(); +await browser.close(); +console.log('🎉 All screenshots done!'); diff --git a/memory-viewer/tsconfig.app.json b/memory-viewer/tsconfig.app.json new file mode 100644 index 0000000..a9b5a59 --- /dev/null +++ b/memory-viewer/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/memory-viewer/tsconfig.json b/memory-viewer/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/memory-viewer/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/memory-viewer/tsconfig.node.json b/memory-viewer/tsconfig.node.json new file mode 100644 index 0000000..8a67f62 --- /dev/null +++ b/memory-viewer/tsconfig.node.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], + "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/memory-viewer/tsconfig.server.json b/memory-viewer/tsconfig.server.json new file mode 100644 index 0000000..e38ee44 --- /dev/null +++ b/memory-viewer/tsconfig.server.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2023", + "lib": ["ES2023"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "esModuleInterop": true, + "skipLibCheck": true, + "strict": true, + "noEmitOnError": false, + "outDir": "./server-dist", + "rootDir": "./server", + "declaration": false, + "sourceMap": false, + "resolveJsonModule": true + }, + "include": ["server/**/*"], + "exclude": ["node_modules", "dist", "server-dist"] +} diff --git a/memory-viewer/vite.config.ts b/memory-viewer/vite.config.ts new file mode 100644 index 0000000..db021c0 --- /dev/null +++ b/memory-viewer/vite.config.ts @@ -0,0 +1,37 @@ +/// +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import tailwindcss from '@tailwindcss/vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + test: { + globals: true, + environment: 'jsdom', + setupFiles: './src/test/setup.ts', + css: false, + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/**', + 'dist/**', + '**/*.d.ts', + '**/*.config.*', + ], + }, + }, + server: { + proxy: { + '/api': { + target: 'http://localhost:3001', + changeOrigin: true, + }, + '/ws': { + target: 'ws://localhost:3001', + ws: true, + }, + } + } +});