diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 77592c6..859956b 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,77 +11,77 @@ concurrency:
cancel-in-progress: true
jobs:
- # ─── Gate 1: Lint (ruff) ───────────────────────────────────────────
- lint:
- name: "Gate 1: Lint (ruff)"
+ quality:
+ name: Quality Gates
runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ python-version: ["3.10", "3.11", "3.12"]
+
steps:
- uses: actions/checkout@v4
- - name: Set up Python 3.12
+ - name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
- python-version: "3.12"
+ python-version: ${{ matrix.python-version }}
+
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: npm
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install ruff
+ python -m pip install -e ".[dev]"
+ npm ci
- - name: Ruff check (linting)
- run: ruff check src/ tests/
+ - name: Ruff check
+ run: python -m ruff check src tests
- - name: Ruff format (formatting)
- run: ruff format --check src/ tests/
+ - name: Ruff format
+ run: python -m ruff format --check src tests
- # ─── Gate 2: Type check (mypy) ─────────────────────────────────────
- typecheck:
- name: "Gate 2: Type check (mypy)"
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
+ - name: Mypy
+ run: python -m mypy src
- - name: Set up Python 3.12
- uses: actions/setup-python@v5
- with:
- python-version: "3.12"
+ - name: Node syntax check
+ run: npm run check
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install mypy
+ - name: Pytest
+ run: python -m pytest -q
- - name: Mypy strict
- run: mypy src/
-
- # ─── Gate 3: Tests (pytest + coverage) ─────────────────────────────
- test:
- name: "Gate 3: Tests (py${{ matrix.python-version }})"
+ package:
+ name: Build & Wheel Smoke
runs-on: ubuntu-latest
- strategy:
- fail-fast: false
- matrix:
- python-version: ["3.10", "3.11", "3.12"]
-
+ needs: quality
steps:
- uses: actions/checkout@v4
- - name: Set up Python ${{ matrix.python-version }}
+ - name: Set up Python
uses: actions/setup-python@v5
with:
- python-version: ${{ matrix.python-version }}
+ python-version: "3.12"
- - name: Install dependencies
+ - name: Build and verify
run: |
- python -m pip install --upgrade pip
- pip install -e ".[dev]"
-
- - name: Run tests with coverage
- run: pytest --cov=roundtable --cov-report=term-missing --cov-report=xml -v
-
- - name: Upload coverage report
- if: matrix.python-version == '3.12'
- uses: actions/upload-artifact@v4
- with:
- name: coverage-report
- path: coverage.xml
+ python -m pip install --upgrade pip build twine
+ python -m build
+ python -m twine check dist/*
+ python -m venv /tmp/roundtable-smoke
+ /tmp/roundtable-smoke/bin/python -m pip install --upgrade pip
+ /tmp/roundtable-smoke/bin/python -m pip install dist/*.whl
+ /tmp/roundtable-smoke/bin/python - <<'PY'
+ from importlib import resources
+ import roundtable
+
+ assert roundtable.__version__ == "2.1.0"
+ assert (resources.files("roundtable") / "web" / "viewer.js").is_file()
+ assert (resources.files("roundtable") / "web" / "i18n.js").is_file()
+ assert (resources.files("roundtable") / "templates" / "product-review.json").is_file()
+ assert (resources.files("roundtable") / "skills" / "mcp-roundtable" / "SKILL.md").is_file()
+ PY
+ /tmp/roundtable-smoke/bin/python -m roundtable.demo --no-web --rounds 1
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
deleted file mode 100644
index bfe99ca..0000000
--- a/.github/workflows/lint.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Lint
-
-on:
- push:
- branches: [main]
- pull_request:
- branches: [main]
-
-jobs:
- ruff:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up Python
- uses: actions/setup-python@v5
- with:
- python-version: "3.11"
-
- - name: Install ruff
- run: pip install ruff
-
- - name: Run ruff check
- run: ruff check src/ tests/
-
- - name: Run ruff format check
- run: ruff format --check src/ tests/
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index acb5293..30c17e1 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -10,9 +10,9 @@ permissions:
id-token: write
jobs:
- # ─── Run Tests (gate before any publishing) ─────────────────────────
- test:
- name: Run tests
+ # ─── Preflight (gate before any publishing) ─────────────────────────
+ preflight:
+ name: Release preflight
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -22,18 +22,19 @@ jobs:
with:
python-version: "3.12"
- - name: Install package with dev extras
- run: |
- python -m pip install --upgrade pip
- python -m pip install -e ".[dev]"
+ - name: Set up Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: "20"
+ cache: npm
- - name: Run pytest
- run: python -m pytest tests/ --ignore=tests/test_web_viewer.py -q
+ - name: Run release preflight
+ run: scripts/release/preflight-check.sh
# ─── Build Python Package ───────────────────────────────────────────
build:
name: Build distribution
- needs: test
+ needs: preflight
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
deleted file mode 100644
index 0c30aff..0000000
--- a/.github/workflows/test.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Test
-
-on:
- push:
- branches: [main]
- pull_request:
- branches: [main]
-
-jobs:
- test:
- runs-on: ubuntu-latest
- strategy:
- matrix:
- python-version: ["3.10", "3.11", "3.12"]
-
- steps:
- - uses: actions/checkout@v4
-
- - name: Set up Python ${{ matrix.python-version }}
- uses: actions/setup-python@v5
- with:
- python-version: ${{ matrix.python-version }}
-
- - name: Install dependencies
- run: |
- python -m pip install --upgrade pip
- pip install -e ".[dev]"
-
- - name: Run tests
- run: pytest --tb=short -q
diff --git a/.gitignore b/.gitignore
index e3b01d6..2d5b545 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,4 +47,3 @@ docs/internal/
# Node.js
node_modules/
-package-lock.json
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ce7fedf..22c69d6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -45,7 +45,7 @@
```bash
# 克隆仓库
git clone https://github.com/MoyuFamily/agent-roundtable.git
-cd roundtable
+cd agent-roundtable
# 创建虚拟环境
python -m venv .venv
@@ -62,28 +62,40 @@ pytest tests/test_core.py
pytest tests/test_core.py::TestCreateDiscussion -v
# 运行 lint
-ruff check .
-ruff format --check .
+ruff check src tests
+ruff format --check src tests
# 类型检查
-mypy src/
+mypy src
```
### Web Viewer 本地开发
-Web viewer 需要 Node.js >= 18。
+Web Viewer 需要 Node.js >= 18。`create_discussion()` 默认会以 `web=True` 尽力启动 Viewer;如果 Node 不存在或依赖安装失败,核心讨论仍应创建成功,并通过 `web_status`、`web_error`、`web_help` 返回诊断。
```bash
# 安装 Node 依赖
npm install
+# 检查 Web Viewer 语法
+npm run check
+
# 运行带 web viewer 的 demo
python -m roundtable.demo --web
+
+# 或显式关闭 web viewer
+python -m roundtable.demo --no-web
```
-可选 Python 依赖(web 功能):`nanoid`、`bcrypt`。通过 `pip install -e ".[web]"` 安装。
+`package-lock.json` 是发布输入的一部分,请提交并维护。首跑自动安装只允许写入项目/包可控目录;不要在代码里无提示安装全局 npm 包。自动安装会设置 `PUPPETEER_SKIP_DOWNLOAD=true`,避免首跑下载 Chromium;PDF 导出缺浏览器时应返回明确错误。
+
+可选 Python 依赖(web 功能):`nanoid`,通过 `pip install -e ".[web]"` 安装。Node 依赖(server.mjs):`bcryptjs`、`md-to-pdf`,已在 `package.json` 中声明;普通运行可用 `npm install --omit=dev`。
-可选 Node 依赖(server.mjs):`bcryptjs`、`md-to-pdf`。已在 package.json 中声明,`npm install` 即可。
+发布前请运行:
+
+```bash
+scripts/release/preflight-check.sh
+```
### Web Viewer Schema 迁移
diff --git a/MANIFEST.in b/MANIFEST.in
index edbb0ad..876cf23 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -6,11 +6,15 @@ include CHANGELOG.md
include SECURITY.md
include pyproject.toml
-recursive-include src/roundtable/web *.html *.css *.mjs *.cjs
+recursive-include src/roundtable/web *.html *.css *.js *.json *.mjs *.cjs
+recursive-include src/roundtable/templates *.json
+recursive-include src/roundtable/skills *.md *.py *.json
+include src/skills/SKILL.md
recursive-include docs *.md
prune docs/internal
prune docs/product
+prune src/skills/references
prune .github
prune .hermes
prune .venv
diff --git a/README.md b/README.md
index 3e8b292..ffbe634 100644
--- a/README.md
+++ b/README.md
@@ -39,6 +39,7 @@ pip install agent-roundtable
| 包名和导入名是什么? | 安装 `agent-roundtable`,代码里 `import roundtable` |
| 能不能独立使用? | 可以,核心库只依赖 Python 标准库和 SQLite |
| 能不能接 Agent 框架? | 可以,通过 adapter 接入 Hermes Agent 或任意 Agent 系统 |
+| Web Viewer 会不会影响首跑? | 默认会尽力启动 Web Viewer;失败不阻断讨论,可看 `web_status` / `web_error` |
| 输出是什么? | 讨论状态、convergence score、共识/分歧、结构化 summary 与结论 |
## 🧭 什么时候用它?
@@ -62,13 +63,13 @@ pip install agent-roundtable
### 安装
-PyPI 发布后,请使用正式包名安装:
+正式包名是 `agent-roundtable`:
```bash
pip install agent-roundtable
```
-发布前或需要验证当前分支时,可从源码安装:
+需要验证当前分支或本地修改时,可从源码安装:
```bash
git clone https://github.com/MoyuFamily/agent-roundtable.git
@@ -117,6 +118,11 @@ result = core.create_discussion(
max_rounds=3,
)
disc_id = result["discussion_id"]
+if result["web_status"] == "ready":
+ print(f"Web Viewer: {result['web_url']}")
+elif result["web_status"] == "failed":
+ print(result["web_error"])
+ print(result["web_help"])
# 2. 参与者发言
core.speak(disc_id, "backend_architect", "PostgreSQL 的 JSON 和事务能力更适合复杂业务建模。")
@@ -135,6 +141,16 @@ print(summary["structured_summary"])
core.end_discussion(disc_id, conclusion="选择 PostgreSQL,优先支持复杂数据结构和长期扩展。")
```
+### 默认 Web Viewer
+
+`create_discussion()` 默认 `web=True`,会尽力启动本地 Web Viewer,这是 Roundtable 的核心体验之一。核心讨论创建优先成功;如果本机没有 Node.js、npm 依赖安装失败或端口不可用,返回值会包含:
+
+- `web_status`: `"ready"`、`"failed"` 或 `"disabled"`
+- `web_url`: Viewer 地址,失败时为 `None`
+- `web_error` / `web_help`: 可读诊断和修复提示
+
+Web Viewer 需要 Node.js 18+。Roundtable 会复用已在线服务、直接启动 `node server.mjs`,并在缺少本地 npm 依赖时尝试 `npm install --omit=dev`;不会自动安装 Node 本体,也不会无提示安装全局 PM2。自动安装会跳过 Puppeteer 的 Chromium 下载;Markdown 导出始终可用,PDF 导出缺浏览器时会返回清晰诊断。
+
### 错误安全模式(推荐用于生产环境)
```python
@@ -174,6 +190,7 @@ result = rt.init(
| 📊 **收敛追踪** | 自动计算每轮共识度(convergence score),量化讨论进展 |
| 🧾 **结构化总结** | 输出共识、分歧、决策建议和结论,适合沉淀会议记录与决策文档 |
| 🔌 **框架无关** | 独立运行,或通过 adapter 接入任何 Agent 框架 |
+| 🖥️ **默认 Web Viewer** | 创建讨论时默认尽力启动实时 Viewer,失败不阻断核心讨论 |
| 🔔 **实时通知** | 讨论事件推送到飞书、Slack 或任意消息平台 |
| 🛡️ **错误安全** | Generic adapter 所有方法返回 dict,永不抛异常 |
| 🗂️ **SQLite 持久化** | 讨论记录持久存储,随时回溯 |
@@ -226,7 +243,6 @@ src/roundtable/
## 🛣️ 后续计划
-- 发布 `agent-roundtable` 到 PyPI
- 补充 CLI 示例和端到端 Demo
- 增强结构化总结模板
- 补充更多 Agent 框架 adapter
diff --git a/README_EN.md b/README_EN.md
index 62cc5c0..76c2242 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -35,6 +35,7 @@ When you let multiple AI agents discuss a complex problem, the hard part is not
| Is the discussion converging? | Automatically track convergence score, consensus points, and disagreement points |
| How are conclusions captured? | Generate structured summaries for meeting notes, PRDs, architecture reviews, and decision records |
| Can it integrate with existing agent systems? | Framework-agnostic; integrate via adapters with Hermes Agent or any agent framework |
+| What about the Web Viewer? | It starts by default on a best-effort path; failures do not block discussion creation |
| Is it heavy to run? | The core library has zero external dependencies and uses only Python stdlib + SQLite |
**In one sentence: `agent-roundtable` is a Python package embeddable in any AI agent system; you pick the participants and define the topic, and it manages multi-agent roundtable discussions, ordered speaking, consensus/disagreement tracking, and structured meeting notes.**
@@ -51,13 +52,13 @@ When you let multiple AI agents discuss a complex problem, the hard part is not
### Installation
-The official PyPI package name is planned as `agent-roundtable`. After release, install it with:
+The official PyPI package name is `agent-roundtable`:
```bash
pip install agent-roundtable
```
-Before the PyPI release, use source installation for local validation:
+Use source installation when you need to validate the current branch or local changes:
```bash
git clone https://github.com/MoyuFamily/agent-roundtable.git
@@ -98,6 +99,11 @@ result = core.create_discussion(
max_rounds=3,
)
disc_id = result["discussion_id"]
+if result["web_status"] == "ready":
+ print(f"Web Viewer: {result['web_url']}")
+elif result["web_status"] == "failed":
+ print(result["web_error"])
+ print(result["web_help"])
# 2. Participants speak
core.speak(disc_id, "backend_architect", "PostgreSQL has stronger JSON and transaction support for complex business modeling.")
@@ -116,6 +122,16 @@ print(summary["structured_summary"])
core.end_discussion(disc_id, conclusion="Choose PostgreSQL for complex data modeling and long-term extensibility.")
```
+### Default Web Viewer
+
+`create_discussion()` defaults to `web=True` and starts the local Web Viewer on a best-effort path. Discussion creation takes priority; if Node.js is missing, npm dependency installation fails, or the port is unavailable, the result includes:
+
+- `web_status`: `"ready"`, `"failed"`, or `"disabled"`
+- `web_url`: viewer URL, or `None` on failure
+- `web_error` / `web_help`: readable diagnostics and next steps
+
+The Web Viewer needs Node.js 18+. Roundtable reuses an existing healthy server, tries `node server.mjs`, and may run local `npm install --omit=dev` for missing web dependencies. It does not install Node itself or silently install global PM2. Automatic install skips Puppeteer's Chromium download; Markdown export always works, while PDF export returns a clear diagnostic when a browser is unavailable.
+
### Error-Safe Mode (Recommended for Production)
```python
@@ -155,6 +171,7 @@ result = rt.init(
| 📊 **Convergence Tracking** | Auto-calculate consensus score per round to quantify discussion progress |
| 🧾 **Structured Summaries** | Output consensus, disagreements, recommendations, and conclusions for meeting notes and decision records |
| 🔌 **Framework Agnostic** | Run standalone or integrate with any agent framework via adapters |
+| 🖥️ **Default Web Viewer** | Best-effort real-time viewer on discussion creation; viewer failures do not block core discussions |
| 🔔 **Real-time Notifications** | Push discussion events to Feishu, Slack, or any messaging platform |
| 🛡️ **Error-Safe** | Generic adapter returns dict for all methods and never throws exceptions |
| 🗂️ **SQLite Persistence** | Persist discussion records for later review and traceability |
@@ -189,7 +206,6 @@ src/roundtable/
## 🛣️ Roadmap
-- Publish PyPI package name: `agent-roundtable` (install with `pip install agent-roundtable`)
- Add CLI examples and end-to-end demos
- Improve structured summary templates
- Add adapters for more agent frameworks
diff --git a/SECURITY.md b/SECURITY.md
index ae1f8b5..520996e 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -4,7 +4,7 @@
| Version | Supported |
| ------- | ------------------ |
-| 0.1.x | :white_check_mark: |
+| 2.1.x | :white_check_mark: |
## Reporting a Vulnerability
diff --git a/SKILL.md b/SKILL.md
index a21b62e..edcb1af 100644
--- a/SKILL.md
+++ b/SKILL.md
@@ -24,7 +24,7 @@ metadata:
## Publishing to skill hubs
-When preparing Roundtable for Hermes Skill Hub or OpenClaw/ClawHub, use the release checklist in `src/skills/references/skill-hub-publishing.md`. Key reminders: keep `src/skills/` self-contained and free of private team data, include both Hermes and OpenClaw metadata blocks in `SKILL.md`, check `hermes skills publish --help`, `clawhub publish --help`, and `clawhub whoami`, and gate real publishing on user confirmation of target account/repo.
+Before publishing to Hermes Skill Hub or OpenClaw/ClawHub, run the repository release preflight and keep the skill package self-contained. Do not include repository-only notes, incident writeups, private chat transcripts, or secret-handling history in the public skill bundle. Confirm the target account/repository before running a real publish.
## Overview
@@ -367,28 +367,17 @@ resp = requests.get('https://open.feishu.cn/open-apis/im/v1/messages',
## Web Viewer (default ON)
-`run_demo()` has `web: bool = True` by default (changed from `False`). This means demo discussions automatically start a web viewer at `http://localhost:8199`. The viewer uses PM2 to manage an Express subprocess, fcntl for file locking, and nanoid for token generation.
+`roundtable_init` / `create_discussion` default to `web=True`. The Web Viewer is best-effort: discussion creation should still succeed when Node.js is missing, npm dependencies cannot be installed, or the viewer port is unavailable.
-**Browser auto-open**: Starting a discussion with `web=True` should automatically open the browser via `subprocess.run(["open", web_url])` in the Hermes adapter's `_handle_init`. This is an **adapter-level side-effect**, not a core-level one — the core library returns the URL but leaves UX actions to the adapter. The generic adapter intentionally does NOT auto-open (headless environments). If you need to customize browser behavior (e.g., open in a specific browser), modify `_handle_init` in `adapters/hermes.py`.
+Returned fields:
-**⚠️ Pitfall: Browser opens at END instead of START (2026-05-23)** — Boss reported the browser doesn't open when the discussion begins; it only opens after the discussion concludes (or manually). The `_handle_init` code has the `subprocess.run(["open", web_url])` call, but it may not fire reliably in all execution paths. **Diagnosis checklist**: (1) Verify `_handle_init` actually reaches the `subprocess.run` line (add logging) (2) Check if the `web_url` variable is correctly populated from `publisher.start()` return (3) Confirm the `open` command runs in the correct subprocess context (may need `shell=False` with list args on macOS). **See also**: Bug task `t_xxxxxxxx` for the specific fix.
+- `web_status`: `ready`, `failed`, or `disabled`
+- `web_url`: viewer URL when ready, otherwise `None`
+- `web_error` and `web_help`: clear diagnostics for viewer startup failures
-**⚠️ Pitfall: WebViewer real-time updates broken on macOS (2026-05-23)** — Boss reported that the WebViewer doesn't show new speeches in real-time; the browser must be force-refreshed to see updates. **Root cause**: The Express server (`server.mjs`) uses `fs.watch(DISCUSSION_PATH, ...)` to detect `discussion.json` changes and broadcast via SSE. But Python's `WebPublisher` uses atomic write: `write .json.tmp → os.rename()`. On macOS, `fs.watch()` on a file loses tracking after `rename()` replaces the inode — the watcher stays on the old inode, never sees the new file's changes. **Fix**: Change `fs.watch` to watch the **directory** instead of the file, then filter by filename:
-```javascript
-// Wrong — breaks on atomic rename:
-watch(DISCUSSION_PATH, () => { ... });
+Startup order: reuse a healthy local viewer, start `node server.mjs`, optionally install local npm dependencies with `npm install --omit=dev`, then retry. PM2 is optional only. Roundtable never installs Node itself and never performs silent global npm installs.
-// Correct — watches directory, catches rename:
-const dir = require("path").dirname(DISCUSSION_PATH);
-const filename = require("path").basename(DISCUSSION_PATH);
-watch(dir, (eventType, changedFilename) => {
- if (changedFilename !== filename) return;
- // ... debounce + broadcast logic unchanged
-});
-```
-**Alternative**: Add server-side polling fallback (`setInterval` + mtime check) as defense-in-depth. **See also**: Bug task `t_xxxxxxxx` for the specific fix.
-
-*Note: Tool calls executed natively on the platform handle environment and browser integration automatically.*
+Browser opening is adapter-level UX. Core and generic APIs return the URL and status fields; headless environments should read those fields instead of assuming a browser window exists.
### Iframe Embed Mode
@@ -434,7 +423,7 @@ Each round is evaluated for convergence:
## Conclusion Document Format
-The format below works for general discussions. For **product/design/dev discussions aimed at producing a buildable specification**, use the decision-oriented format instead — see `src/skills/references/web-viewer-discussion-example.md` for the full pattern (MVP scope, tech architecture, acceptance criteria, risk assessment, design deliverables, action items).
+The format below works for general discussions. For **product/design/dev discussions aimed at producing a buildable specification**, prefer a decision-oriented format with MVP scope, technical architecture, acceptance criteria, risk assessment, design deliverables, and action items.
```markdown
# Roundtable Conclusion: [Topic]
@@ -493,24 +482,3 @@ kanban_comment(task_id="t_xxx", body="Roundtable conclusion: {conclusion_path}")
9. **WebViewer integration** — Setting `web=True` on `roundtable_init` automatically starts the WebViewer. It is supported across processes.
10. **Notifications config** — Ensure you pass the `notifications` configuration parameter to `roundtable_init` if you want events to sync to messaging groups (e.g. Feishu). The adapter handles `send_fn` wiring automatically.
11. **Developer Reference** — Maintainer-only implementation documentation exists separately and is not part of the agent execution workflow.
-
-## Test Results
-
-See `src/skills/references/test-results-2026-05-20.md` for the first functional test results, including bugs found and product acceptance report.
-
-## Open-Source Release
-
-See `src/skills/references/open-source-readiness.md` for the pre-release checklist (LICENSE, cleanup, adapter gaps, test isolation).
-
-## Working Examples
-
-- `src/skills/references/opc-experience-discussion-example.md` — 4-round, 4-participant discussion with timing data and workflow
-- `src/skills/references/notifications-example.md` — roundtable with real-time push notifications to Feishu
-- `src/skills/references/release-planning-discussion.md` — 3-round product/design/dev discussion for open-source release planning
-- `src/skills/references/ai-relay-open-source-discussion.md` — 3-round discussion with standard tool workflow, notifications, and conclusion doc → 5/29 release plan
-- `src/skills/references/web-viewer-discussion-example.md` — Decision-oriented conclusion doc pattern: MVP scope, tech architecture, acceptance criteria, risk assessment, design deliverables. Use this format when the discussion goal is to produce a buildable specification.
-- `src/skills/references/post-discussion-kanban-dispatch.md` — After discussion concludes, create kanban tasks grouped by owner, subscribe notifications, and dispatch to team via Feishu groups.
-
-## Open-Source Release Checklist
-
-See `src/skills/references/open-source-readiness-checklist.md` for the pre-release audit: missing LICENSE, Hermes-specific files to separate, build-backend fix, .gitignore, internal docs to remove, generic adapter gaps, and target package structure.
diff --git a/landing/index.html b/landing/index.html
index da0d3f6..ff3af0c 100644
--- a/landing/index.html
+++ b/landing/index.html
@@ -22,12 +22,12 @@
- Multi-agent discussion engine多 Agent 圆桌讨论引擎
Roundtable 让多个 AI Agent 像开会一样围绕同一问题按轮次发言,自动追踪共识与分歧,并生成结构化会议记录和结论。
Structured roundtable discussions for AI Agent teams — from scattered responses to decision-ready meeting notes.
pip install agent-roundtable
Python 3.10+Apache-2.0Zero external depsSQLite ready
+ Multi-agent discussion engine多 Agent 圆桌讨论引擎
Roundtable 让多个 AI Agent 像开会一样围绕同一问题按轮次发言,自动追踪共识与分歧,并生成结构化会议记录和结论。
Structured roundtable discussions for AI Agent teams — from scattered responses to decision-ready meeting notes.
pip install agent-roundtable
Python 3.10+Apache-2.0Core zero depsDefault Web Viewer
多 Agent 不缺发言,缺的是一张能收敛的圆桌
当多个 Agent 同时参与产品、技术或代码评审时,Roundtable 提供讨论边界、发言顺序、分歧记录和结论沉淀。
PAIN 01会说话,但不会“开会”
多个 Agent 的输出容易变成平行独白,缺少 topic、participants、rounds 这些会议边界。
$ agents speak... but no protocol
PAIN 02讨论过程难复盘
观点和反驳散落在聊天记录里,缺少结构化 consensus / disagreement 供后续查询。
status: context fragmented
PAIN 03结论难沉淀到工作流
产品决策、技术评审、代码 Review 都需要可追溯 summary,而不是人工再整理一次。
output: decision-ready notes
从讨论生命周期到结构化纪要
Roundtable 是轻量会议协议层:创建 discussion、组织 turns、追踪 convergence,并输出可复盘的 meeting notes。
01Create Discussion
输入 topic、participants、max_rounds,获得有边界的 discussion 状态。
02Speak in Turns
按角色和轮次记录每次发言,让多方观点不再混作一团。
03Track Convergence
区分 consensus points 与 disagreement points,展示讨论是否正在收敛。
04Summarize Decisions
输出 summary 与 conclusion,形成可复盘、可嵌入工作流的决策资产。
一条清晰的会议协议流
从初始化议题到结论沉淀,每一步都有可追踪状态,适合嵌入现有 Agent runtime。
Initialize
topic + participants
Round-based speaking
ordered turns
Track status
consensus / risks
Conclude
summary + notes
- Developer-first CTA用一条 pip 命令接入现有 Agent 系统
包名是 agent-roundtable,导入名是 roundtable。核心采用标准库与 SQLite 持久化,适合直接嵌入 Python 项目。
Package: agent-roundtableImport: roundtableSource install: pip install -e .
pip install agent-roundtable
+ Developer-first CTA用一条 pip 命令接入现有 Agent 系统
包名是 agent-roundtable,导入名是 roundtable。核心采用标准库与 SQLite 持久化;默认 Web Viewer 会尽力启动,失败也不阻断讨论创建。
Package: agent-roundtableImport: roundtableNode.js 18+ for Web Viewer
pip install agent-roundtable
from roundtable import RoundtableCore
@@ -40,10 +40,11 @@
{"profile": "product", "role": "产品经理"},
],
max_rounds=3,
-)
+)
+print(result["web_status"], result.get("web_url"))
适合被讨论,而不是只被回答的问题
适合需要多角色观点碰撞、分歧可见、结论可追溯的复杂问题。
USE产品决策
产品、设计、开发、增长讨论 MVP 边界,输出优先级和暂缓项。
USE技术评审
架构、后端、运维、安全收敛数据库、部署与权限方案。
USE代码 Review
质量、安全、性能、可维护性多角度审查,输出阻塞问题。
USE需求澄清
多个专家角色追问模糊需求,沉淀共识、风险与未决问题。
USEAgent 工作流
作为 coordinator 的讨论协议层,留下可查询会议记录。
- 一套圆桌协议,多处 Agent 生态可用
从 GitHub 源码到 PyPI 包,再到 Hermes Skill Hub 与 OpenClaw Skill Hub,统一作为多 Agent 圆桌协议入口。
SOURCEGitHub
源码、README、Issue 与贡献入口。
Available
Open repository →PACKAGEPyPI
面向 Python 开发者的正式安装入口。
Coming soon
Package page pendingECOSYSTEMHermes Skill Hub
面向 Hermes Agent 用户的技能生态入口。
Planned
Skill listing plannedECOSYSTEMOpenClaw Skill Hub
面向 OpenClaw 用户的技能生态入口。
Planned
Skill listing planned
+ 一套圆桌协议,多处 Agent 生态可用
从 GitHub 源码到 PyPI 包,再到 Skill Hub 生态,统一作为多 Agent 圆桌协议入口。
SOURCEGitHub
源码、README、Issue 与贡献入口。
Available
Open repository →PACKAGEPyPI
面向 Python 开发者的正式安装入口。
Available
Open package →ECOSYSTEMHermes Skill Hub
面向 Hermes Agent 用户的技能生态入口。
In preparation
Release packaging readyECOSYSTEMOpenClaw Skill Hub
面向 OpenClaw 用户的技能生态入口。
In preparation
Release packaging ready
把一次性的多 Agent 对话,变成可追踪、可复盘、可沉淀的圆桌讨论。
用 Roundtable 给产品决策、技术评审、代码 Review 和多 Agent 工作流留下一份结构化决策资产。
pip install agent-roundtable
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..0d916fa
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,2916 @@
+{
+ "name": "roundtable-web-viewer",
+ "version": "2.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "roundtable-web-viewer",
+ "version": "2.1.0",
+ "dependencies": {
+ "bcryptjs": "^3.0.3",
+ "express": "^5.1.0",
+ "md-to-pdf": "^5.2.4"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@samverschueren/stream-to-observable": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
+ "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==",
+ "dependencies": {
+ "any-observable": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependenciesMeta": {
+ "rxjs": {
+ "optional": true
+ },
+ "zen-observable": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tootallnate/quickjs-emscripten": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
+ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.9.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz",
+ "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "undici-types": ">=7.24.0 <7.24.7"
+ }
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-observable": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
+ "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="
+ },
+ "node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/ast-types": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
+ "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ast-types/node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/b4a": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
+ "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/bare-events": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
+ "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "bare-abort-controller": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-fs": {
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz",
+ "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.5.4",
+ "bare-path": "^3.0.0",
+ "bare-stream": "^2.6.4",
+ "bare-url": "^2.2.2",
+ "fast-fifo": "^1.3.2"
+ },
+ "engines": {
+ "bare": ">=1.16.0"
+ },
+ "peerDependencies": {
+ "bare-buffer": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-buffer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-os": {
+ "version": "3.9.1",
+ "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz",
+ "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "bare": ">=1.14.0"
+ }
+ },
+ "node_modules/bare-path": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz",
+ "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-os": "^3.0.1"
+ }
+ },
+ "node_modules/bare-stream": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz",
+ "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "streamx": "^2.25.0",
+ "teex": "^1.0.1"
+ },
+ "peerDependencies": {
+ "bare-abort-controller": "*",
+ "bare-buffer": "*",
+ "bare-events": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ },
+ "bare-buffer": {
+ "optional": true
+ },
+ "bare-events": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-url": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz",
+ "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/basic-ftp": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+ "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
+ "dependencies": {
+ "restore-cursor": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
+ "integrity": "sha512-f4r4yJnbT++qUPI9NR4XLDLq41gQ+uqnPItWG0F5ZkehuNiTTa3EY0S4AqTSUOeJ7/zU41oWPQSNkW5BqPL9bg==",
+ "dependencies": {
+ "slice-ansi": "0.0.4",
+ "string-width": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "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/code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "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=="
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz",
+ "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==",
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.1",
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cosmiconfig/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/cosmiconfig/node_modules/js-yaml": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/elegant-spinner": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
+ "integrity": "sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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=="
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "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==",
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "is-extendable": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "license": "MIT"
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/figures": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+ "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5",
+ "object-assign": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-port": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
+ "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stdin": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
+ "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gray-matter": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz",
+ "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==",
+ "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/has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/highlight.js": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
+ "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+ "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
+ "dependencies": {
+ "number-is-nan": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-observable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz",
+ "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==",
+ "dependencies": {
+ "symbol-observable": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
+ },
+ "node_modules/is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "3.14.2",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
+ "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "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==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/listr": {
+ "version": "0.14.3",
+ "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
+ "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==",
+ "dependencies": {
+ "@samverschueren/stream-to-observable": "^0.3.0",
+ "is-observable": "^1.1.0",
+ "is-promise": "^2.1.0",
+ "is-stream": "^1.1.0",
+ "listr-silent-renderer": "^1.1.1",
+ "listr-update-renderer": "^0.5.0",
+ "listr-verbose-renderer": "^0.5.0",
+ "p-map": "^2.0.0",
+ "rxjs": "^6.3.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/listr-silent-renderer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
+ "integrity": "sha512-L26cIFm7/oZeSNVhWB6faeorXhMg4HNlb/dS/7jHhr708jxlXrtrBWo4YUxZQkc6dGoxEAe6J/D3juTRBUzjtA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-update-renderer": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz",
+ "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==",
+ "dependencies": {
+ "chalk": "^1.1.3",
+ "cli-truncate": "^0.2.1",
+ "elegant-spinner": "^1.0.1",
+ "figures": "^1.7.0",
+ "indent-string": "^3.0.0",
+ "log-symbols": "^1.0.2",
+ "log-update": "^2.3.0",
+ "strip-ansi": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "listr": "^0.14.2"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/listr-verbose-renderer": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz",
+ "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==",
+ "dependencies": {
+ "chalk": "^2.4.1",
+ "cli-cursor": "^2.1.0",
+ "date-fns": "^1.27.2",
+ "figures": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "node_modules/listr-verbose-renderer/node_modules/figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr/node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+ },
+ "node_modules/log-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
+ "integrity": "sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==",
+ "dependencies": {
+ "chalk": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/log-update": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz",
+ "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==",
+ "dependencies": {
+ "ansi-escapes": "^3.0.0",
+ "cli-cursor": "^2.0.0",
+ "wrap-ansi": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/marked": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
+ "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/md-to-pdf": {
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/md-to-pdf/-/md-to-pdf-5.2.5.tgz",
+ "integrity": "sha512-TG8TgDM0PmEwCldR6j/1QP9gBElLL3DSn5ID8P3bEXEl3Y2zHOUSyszHzabWnDNxklRjKbi40ybli8YQJ5Ym5w==",
+ "dependencies": {
+ "arg": "^5.0.2",
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.2",
+ "get-port": "^5.1.1",
+ "get-stdin": "^8.0.0",
+ "gray-matter": "^4.0.3",
+ "highlight.js": "^11.7.0",
+ "iconv-lite": "^0.6.3",
+ "listr": "^0.14.3",
+ "marked": "^4.2.12",
+ "puppeteer": ">=8.0.0",
+ "semver": "^7.3.7",
+ "serve-handler": "^6.1.3"
+ },
+ "bin": {
+ "md-to-pdf": "dist/cli.js",
+ "md2pdf": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/@puppeteer/browsers": {
+ "version": "2.13.2",
+ "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz",
+ "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "extract-zip": "^2.0.1",
+ "progress": "^2.0.3",
+ "proxy-agent": "^6.5.0",
+ "semver": "^7.7.4",
+ "tar-fs": "^3.1.1",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "browsers": "lib/cjs/main-cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/md-to-pdf/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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/chromium-bidi": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz",
+ "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mitt": "^3.0.1",
+ "zod": "^3.24.1"
+ },
+ "peerDependencies": {
+ "devtools-protocol": "*"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/data-uri-to-buffer": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
+ "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/degenerator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
+ "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.13.4",
+ "escodegen": "^2.1.0",
+ "esprima": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/devtools-protocol": {
+ "version": "0.0.1608973",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz",
+ "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/md-to-pdf/node_modules/get-uri": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
+ "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
+ "license": "MIT",
+ "dependencies": {
+ "basic-ftp": "^5.0.2",
+ "data-uri-to-buffer": "^6.0.2",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/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==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/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==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/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==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/pac-proxy-agent": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
+ "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/quickjs-emscripten": "^0.23.0",
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "get-uri": "^6.0.1",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.6",
+ "pac-resolver": "^7.0.1",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/pac-resolver": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
+ "license": "MIT",
+ "dependencies": {
+ "degenerator": "^5.0.0",
+ "netmask": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/proxy-agent": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
+ "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "http-proxy-agent": "^7.0.1",
+ "https-proxy-agent": "^7.0.6",
+ "lru-cache": "^7.14.1",
+ "pac-proxy-agent": "^7.1.0",
+ "proxy-from-env": "^1.1.0",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/md-to-pdf/node_modules/puppeteer": {
+ "version": "24.43.1",
+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.43.1.tgz",
+ "integrity": "sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.13.2",
+ "chromium-bidi": "14.0.0",
+ "cosmiconfig": "^9.0.0",
+ "devtools-protocol": "0.0.1608973",
+ "puppeteer-core": "24.43.1",
+ "typed-query-selector": "^2.12.2"
+ },
+ "bin": {
+ "puppeteer": "lib/cjs/puppeteer/node/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/puppeteer-core": {
+ "version": "24.43.1",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz",
+ "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.13.2",
+ "chromium-bidi": "14.0.0",
+ "debug": "^4.4.3",
+ "devtools-protocol": "0.0.1608973",
+ "typed-query-selector": "^2.12.2",
+ "webdriver-bidi-protocol": "0.4.1",
+ "ws": "^8.20.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/socks-proxy-agent": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
+ "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "socks": "^2.8.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/md-to-pdf/node_modules/webdriver-bidi-protocol": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz",
+ "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="
+ },
+ "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=="
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/netmask": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz",
+ "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
+ "dependencies": {
+ "mimic-fn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w=="
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
+ "dependencies": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+ "dependencies": {
+ "tslib": "^1.9.0"
+ },
+ "engines": {
+ "npm": ">=2.0.0"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/section-matter": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz",
+ "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==",
+ "dependencies": {
+ "extend-shallow": "^2.0.1",
+ "kind-of": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.8.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.3.tgz",
+ "integrity": "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg==",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-handler": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz",
+ "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==",
+ "dependencies": {
+ "bytes": "3.0.0",
+ "content-disposition": "0.5.2",
+ "mime-types": "2.1.18",
+ "minimatch": "3.1.5",
+ "path-is-inside": "1.0.2",
+ "path-to-regexp": "3.3.0",
+ "range-parser": "1.2.0"
+ }
+ },
+ "node_modules/serve-handler/node_modules/bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-handler/node_modules/content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "dependencies": {
+ "mime-db": "~1.33.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/path-to-regexp": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
+ "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw=="
+ },
+ "node_modules/serve-handler/node_modules/range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
+ },
+ "node_modules/slice-ansi": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+ "integrity": "sha512-up04hB2hR92PgjpyU3y/eg91yIBILyjVY26NvvciY3EVVPjybkMszMpXQ9QAkcS3I5rtJBDLoTxxg+qvW8c7rw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
+ "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.1.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
+ },
+ "node_modules/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/streamx": {
+ "version": "2.27.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.27.0.tgz",
+ "integrity": "sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==",
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
+ "dependencies": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tar-fs": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
+ "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0",
+ "tar-stream": "^3.1.5"
+ },
+ "optionalDependencies": {
+ "bare-fs": "^4.0.1",
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz",
+ "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==",
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "bare-fs": "^4.5.5",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
+ "node_modules/teex": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
+ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
+ "license": "MIT",
+ "dependencies": {
+ "streamx": "^2.12.5"
+ }
+ },
+ "node_modules/text-decoder": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
+ "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ },
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typed-query-selector": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz",
+ "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ=="
+ },
+ "node_modules/undici-types": {
+ "version": "7.24.6",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz",
+ "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==",
+ "dependencies": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dependencies": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
+ "dependencies": {
+ "ansi-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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=="
+ },
+ "node_modules/ws": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
+ "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/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "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==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index a05c6a3..7cc22bc 100644
--- a/package.json
+++ b/package.json
@@ -7,15 +7,22 @@
"scripts": {
"start": "node src/roundtable/web/server.mjs",
"dev": "node --watch src/roundtable/web/server.mjs",
+ "check": "node --check src/roundtable/web/server.mjs && node --check src/roundtable/web/viewer.js",
"release": "bash scripts/release/release.sh",
"release:dry": "bash scripts/release/release.sh --dry-run",
"release:patch": "bash scripts/release/release.sh --type=patch",
"release:minor": "bash scripts/release/release.sh --type=minor",
"release:major": "bash scripts/release/release.sh --type=major"
},
+ "engines": {
+ "node": ">=18"
+ },
"dependencies": {
"bcryptjs": "^3.0.3",
"express": "^5.1.0",
"md-to-pdf": "^5.2.4"
+ },
+ "overrides": {
+ "puppeteer": "24.43.1"
}
-}
\ No newline at end of file
+}
diff --git a/pyproject.toml b/pyproject.toml
index 2d648c0..40db064 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -34,8 +34,8 @@ classifiers = [
"Typing :: Typed",
]
-# Minimal dependencies — bcrypt for password hashing
-dependencies = ["bcrypt>=4.0.0"]
+# Minimal core: stdlib + SQLite only. Optional extras add integration helpers.
+dependencies = []
[project.urls]
Homepage = "https://github.com/MoyuFamily/agent-roundtable"
@@ -63,14 +63,19 @@ dev = [
[tool.setuptools.packages.find]
where = ["src"]
+include = ["roundtable*"]
[tool.setuptools.package-data]
"roundtable" = [
"py.typed",
"web/*.html",
"web/*.css",
+ "web/*.js",
+ "web/package*.json",
"web/*.mjs",
"web/*.cjs",
+ "templates/*.json",
+ "skills/mcp-roundtable/*.md",
"skills/mcp-roundtable/SKILL.md",
"skills/mcp-roundtable/install.py",
"skills/mcp-roundtable/configs/*.json",
diff --git a/quick_demo.py b/quick_demo.py
index 49eceaf..7e8d3af 100644
--- a/quick_demo.py
+++ b/quick_demo.py
@@ -13,3 +13,12 @@
core = RoundtableCore()
result = core.run_demo()
+
+print("\nDemo complete")
+print(f"Discussion ID: {result['discussion_id']}")
+print(f"Web status: {result.get('web_status')}")
+if result.get("web_url"):
+ print(f"Web viewer: {result['web_url']}")
+elif result.get("web_error"):
+ print(f"Web error: {result['web_error']}")
+ print(result.get("web_help", ""))
diff --git a/scripts/release/preflight-check.sh b/scripts/release/preflight-check.sh
index 8134d96..aab91a7 100755
--- a/scripts/release/preflight-check.sh
+++ b/scripts/release/preflight-check.sh
@@ -1,12 +1,10 @@
#!/usr/bin/env bash
-# preflight-check.sh — Run all checks before release.
-# Exits non-zero if any check fails.
+# preflight-check.sh — Run all release gates before publishing.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/../.." && pwd)"
-
cd "$ROOT_DIR"
RED='\033[0;31m'
@@ -17,14 +15,43 @@ NC='\033[0m'
pass() { echo -e "${GREEN}✓${NC} $1"; }
fail() { echo -e "${RED}✗${NC} $1"; exit 1; }
warn() { echo -e "${YELLOW}⚠${NC} $1"; }
-info() { echo -e " $1"; }
+info() { echo " $1"; }
+run() {
+ local label="$1"
+ echo ""
+ echo "▶ $label"
+ shift
+ "$@"
+ pass "$label passed"
+}
+
+if [ -n "${PYTHON:-}" ]; then
+ PYTHON_BIN="$PYTHON"
+elif [ -x ".venv/bin/python" ]; then
+ PYTHON_BIN=".venv/bin/python"
+elif command -v python3.12 >/dev/null 2>&1; then
+ PYTHON_BIN="$(command -v python3.12)"
+elif command -v python3.11 >/dev/null 2>&1; then
+ PYTHON_BIN="$(command -v python3.11)"
+elif command -v python3.10 >/dev/null 2>&1; then
+ PYTHON_BIN="$(command -v python3.10)"
+else
+ PYTHON_BIN="${PYTHON:-python3}"
+fi
echo "═══════════════════════════════════"
echo " Release Preflight Checks"
echo "═══════════════════════════════════"
-echo ""
-# 1. Git clean
+if ! "$PYTHON_BIN" - <<'PY' >/dev/null; then
+import sys
+
+raise SystemExit(0 if sys.version_info >= (3, 10) else 1)
+PY
+ fail "Python 3.10+ is required before releasing. Set PYTHON=/path/to/python3.11 if needed."
+fi
+
+echo ""
echo "▶ Checking git status..."
DIRTY=$(git status --porcelain)
if [ -n "$DIRTY" ]; then
@@ -32,116 +59,91 @@ if [ -n "$DIRTY" ]; then
fi
pass "Git working directory clean"
-# 2. On a valid branch
BRANCH=$(git branch --show-current)
if [ -z "$BRANCH" ]; then
fail "Not on a branch (detached HEAD)."
fi
info "Branch: $BRANCH"
-
-# 3. Up to date with remote
-git fetch --quiet 2>/dev/null || warn "Could not fetch from remote (offline?)"
-LOCAL=$(git rev-parse HEAD)
+git fetch --quiet 2>/dev/null || warn "Could not fetch from remote"
REMOTE=$(git rev-parse "origin/$BRANCH" 2>/dev/null || echo "")
-if [ -n "$REMOTE" ] && [ "$LOCAL" != "$REMOTE" ]; then
+if [ -n "$REMOTE" ] && [ "$(git rev-parse HEAD)" != "$REMOTE" ]; then
warn "Local branch is not in sync with origin/$BRANCH"
fi
-pass "Branch check passed"
-# 4. Python tests (this is a Python package — pytest is authoritative)
echo ""
-echo "▶ Running Python tests..."
-# Prefer `python -m pytest` so we never depend on a console-script shebang
-# that may be stale (e.g. when the venv was moved or the repo renamed).
-if [ -x ".venv/bin/python" ]; then
- PYTEST_BIN=".venv/bin/python -m pytest"
-elif command -v python3 >/dev/null 2>&1 && python3 -c "import pytest" 2>/dev/null; then
- PYTEST_BIN="python3 -m pytest"
-elif command -v pytest >/dev/null 2>&1; then
- PYTEST_BIN=pytest
-else
- fail "pytest not found. Install with: pip install -e '.[dev]'"
-fi
+echo "▶ Installing release check tools..."
+"$PYTHON_BIN" -m pip install --upgrade pip build twine >/dev/null
+"$PYTHON_BIN" -m pip install -e ".[dev]" >/dev/null
+pass "Release tools installed"
-if $PYTEST_BIN tests/ --ignore=tests/test_web_viewer.py -q; then
- pass "Python tests passed"
-else
- fail "Python tests failed"
-fi
+run "Ruff lint" "$PYTHON_BIN" -m ruff check src tests
+run "Ruff format" "$PYTHON_BIN" -m ruff format --check src tests
+run "Mypy" "$PYTHON_BIN" -m mypy src
-# 5. npm tests (if present)
-echo ""
-echo "▶ Running npm tests..."
if [ -f "package.json" ]; then
- SCRIPTS=$(node -e "const p=require('./package.json'); console.log(Object.keys(p.scripts||{}).join(','))")
- if echo "$SCRIPTS" | grep -q "test"; then
- npm test 2>&1 && pass "npm tests passed" || fail "npm tests failed"
+ if command -v npm >/dev/null 2>&1; then
+ NODE_MAJOR=$(node -e "const major=Number(process.versions.node.split('.')[0]); process.stdout.write(String(major));")
+ if [ "$NODE_MAJOR" -lt 18 ]; then
+ fail "Node.js 18+ is required before releasing. Current version: $(node --version)"
+ fi
+ echo ""
+ echo "▶ Installing npm dependencies..."
+ npm install --omit=dev
+ pass "npm dependencies installed"
+ run "Node syntax check" npm run check
else
- warn "No npm test script found — skipping"
+ fail "npm not found. Install Node.js 18+ before releasing."
fi
-else
- warn "No package.json found — skipping npm tests"
fi
-# 6. Lint
-echo ""
-echo "▶ Running lint..."
-if [ -f "package.json" ]; then
- if echo "${SCRIPTS:-}" | grep -q "lint"; then
- npm run lint 2>&1 && pass "Lint passed" || fail "Lint failed"
- else
- warn "No lint script found — skipping"
- fi
-fi
+run "Pytest" "$PYTHON_BIN" -m pytest -q
-# 7. Build
-echo ""
-echo "▶ Running build..."
-if [ -f "package.json" ]; then
- if echo "${SCRIPTS:-}" | grep -q "build"; then
- npm run build 2>&1 && pass "Build passed" || fail "Build failed"
- else
- warn "No build script found — skipping"
- fi
-fi
-
-# 8. Version consistency across package.json, pyproject.toml, SKILL.md, src/skills/SKILL.md
echo ""
echo "▶ Checking version consistency..."
-
PKG_VERSION=$(node -e "console.log(require('./package.json').version)")
-info "package.json version: $PKG_VERSION"
-
-PYPROJECT_VERSION=""
-if [ -f "pyproject.toml" ]; then
- PYPROJECT_VERSION=$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/^version = "(.+)"/\1/')
- info "pyproject.toml version: $PYPROJECT_VERSION"
-fi
-
-SKILL_VERSION=""
-if [ -f "SKILL.md" ]; then
- SKILL_VERSION=$(grep -E '^version: ' SKILL.md | head -1 | sed -E 's/^version: //')
- info "SKILL.md version: $SKILL_VERSION"
-fi
-
-SRC_SKILL_VERSION=""
-if [ -f "src/skills/SKILL.md" ]; then
- SRC_SKILL_VERSION=$(grep -E '^version: ' src/skills/SKILL.md | head -1 | sed -E 's/^version: //')
- info "src/skills/SKILL.md version: $SRC_SKILL_VERSION"
-fi
-
-# All non-empty versions must match package.json
-MISMATCH=0
-for v in "$PYPROJECT_VERSION" "$SKILL_VERSION" "$SRC_SKILL_VERSION"; do
- if [ -n "$v" ] && [ "$v" != "$PKG_VERSION" ]; then
- MISMATCH=1
+WEB_PKG_VERSION=$(node -e "console.log(require('./src/roundtable/web/package.json').version)")
+PYPROJECT_VERSION=$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/^version = "(.+)"/\1/')
+INIT_VERSION=$("$PYTHON_BIN" -c "import roundtable; print(roundtable.__version__)")
+SKILL_VERSION=$(grep -E '^version: ' SKILL.md | head -1 | sed -E 's/^version: //')
+SRC_SKILL_VERSION=$(grep -E '^version: ' src/skills/SKILL.md | head -1 | sed -E 's/^version: //')
+for item in "$WEB_PKG_VERSION" "$PYPROJECT_VERSION" "$INIT_VERSION" "$SKILL_VERSION" "$SRC_SKILL_VERSION"; do
+ if [ "$item" != "$PKG_VERSION" ]; then
+ fail "Version mismatch: package.json=$PKG_VERSION web/package.json=$WEB_PKG_VERSION pyproject=$PYPROJECT_VERSION __init__=$INIT_VERSION SKILL=$SKILL_VERSION src/skills=$SRC_SKILL_VERSION"
fi
done
-if [ $MISMATCH -eq 1 ]; then
- fail "Version mismatch — all of package.json, pyproject.toml, SKILL.md, src/skills/SKILL.md must agree."
-fi
pass "Version check passed"
+echo ""
+echo "▶ Building distributions..."
+rm -rf dist build
+"$PYTHON_BIN" -m build
+pass "Build passed"
+run "Twine check" "$PYTHON_BIN" -m twine check dist/*
+
+echo ""
+echo "▶ Wheel install smoke test..."
+SMOKE_DIR=$(mktemp -d)
+"$PYTHON_BIN" -m venv "$SMOKE_DIR/venv"
+"$SMOKE_DIR/venv/bin/python" -m pip install --upgrade pip >/dev/null
+"$SMOKE_DIR/venv/bin/python" -m pip install dist/*.whl >/dev/null
+"$SMOKE_DIR/venv/bin/python" - <<'PY'
+from importlib import resources
+
+import roundtable
+
+assert roundtable.__version__ == "2.1.0"
+assert (resources.files("roundtable") / "web" / "viewer.js").is_file()
+assert (resources.files("roundtable") / "web" / "i18n.js").is_file()
+assert (resources.files("roundtable") / "web" / "package.json").is_file()
+assert (resources.files("roundtable") / "web" / "package-lock.json").is_file()
+assert (resources.files("roundtable") / "templates" / "product-review.json").is_file()
+assert (resources.files("roundtable") / "skills" / "mcp-roundtable" / "SKILL.md").is_file()
+print("wheel smoke ok")
+PY
+"$SMOKE_DIR/venv/bin/python" -m roundtable.demo --no-web --rounds 1 >/dev/null
+rm -rf "$SMOKE_DIR"
+pass "Wheel install smoke test passed"
+
echo ""
echo "═══════════════════════════════════"
echo -e " ${GREEN}All preflight checks passed ✓${NC}"
diff --git a/src/roundtable/__init__.py b/src/roundtable/__init__.py
index 4f27d2a..5ddae31 100644
--- a/src/roundtable/__init__.py
+++ b/src/roundtable/__init__.py
@@ -48,7 +48,7 @@
from roundtable.web_publisher import WebPublisher
from roundtable.webhook import WebhookSender
-__version__ = "0.4.0"
+__version__ = "2.1.0"
# ---------------------------------------------------------------------------
# Adapter registry
diff --git a/src/roundtable/core.py b/src/roundtable/core.py
index 05057fe..e945dc4 100644
--- a/src/roundtable/core.py
+++ b/src/roundtable/core.py
@@ -13,7 +13,7 @@
from typing import Any, ClassVar
from roundtable.db import RoundtableDB
-from roundtable.demo import (
+from roundtable.demo_data import (
DEMO_FINDINGS,
DEMO_PARTICIPANTS,
DEMO_SPEECHES,
@@ -147,10 +147,14 @@ def create_discussion(
status=status,
)
- # Optionally start web viewer
+ # Optionally start web viewer. Web is a product default, but it is
+ # intentionally best-effort so core discussion creation still works.
web_url = None
+ web_status = "disabled"
+ web_error = None
+ web_help = None
if web:
- from roundtable.web_publisher import WebPublisher
+ from roundtable.web_publisher import WEB_HELP, WebPublisher
output_dir = self._get_web_dir(disc.id)
try:
@@ -172,11 +176,14 @@ def create_discussion(
status=disc.status,
expires_at=expires_at,
)
+ web_status = "ready"
+ self._publishers[disc.id] = publisher
+ logger.info("Web viewer started for discussion %s: %s", disc.id, web_url)
except Exception as exc:
+ web_status = "failed"
+ web_error = str(exc)
+ web_help = WEB_HELP
logger.exception("Failed to start web viewer for discussion %s", disc.id)
- raise RuntimeError(f"Failed to start web viewer: {exc}") from exc
- self._publishers[disc.id] = publisher
- logger.info("Web viewer started for discussion %s: %s", disc.id, web_url)
return {
"ok": True,
@@ -187,6 +194,9 @@ def create_discussion(
"speech_order": disc.speech_order,
"status": disc.status,
"web_url": web_url,
+ "web_status": web_status,
+ "web_error": web_error,
+ "web_help": web_help,
}
finally:
conn.close()
diff --git a/src/roundtable/demo.py b/src/roundtable/demo.py
index 4ed65c1..5c87d83 100644
--- a/src/roundtable/demo.py
+++ b/src/roundtable/demo.py
@@ -1,109 +1,13 @@
from __future__ import annotations
+import argparse
import logging
import textwrap
from typing import Any
-logger = logging.getLogger(__name__)
+from roundtable.demo_data import DEMO_FINDINGS, DEMO_PARTICIPANTS, DEMO_SPEECHES, DEMO_TOPIC
-DEMO_TOPIC: str = "选择后端框架:FastAPI vs Go Gin vs Node Express"
-DEMO_PARTICIPANTS: list[dict[str, Any]] = [
- {
- "profile": "alice",
- "role": "全栈工程师",
- "display_name": "Alice",
- "perspective": "重视开发效率和生态",
- "avatar": "👩💻",
- "title": "Senior Full-Stack Engineer",
- "description": (
- "10 年全栈经验,主导过多个从零到一的产品架构。擅长 Python/TypeScript 全栈,关注开发者体验和交付效率。"
- ),
- },
- {
- "profile": "bob",
- "role": "架构师",
- "display_name": "Bob",
- "perspective": "重视性能和可维护性",
- "avatar": "🏗️",
- "title": "Principal Architect",
- "description": "分布式系统架构专家,专注于高可用和水平扩展方案。擅长 Go/Rust 技术栈,推崇简洁的系统设计哲学。",
- },
- {
- "profile": "carol",
- "role": "产品经理",
- "display_name": "Carol",
- "perspective": "重视交付速度 and 团队学习成本",
- "avatar": "📊",
- "title": "Product Director",
- "description": "5 年 B 端产品经验,关注技术选型对业务交付的影响。擅长平衡技术理想与业务现实,推动敏捷迭代。",
- },
-]
-DEMO_SPEECHES: dict[int, dict[str, str]] = {
- 1: {
- "alice": (
- "FastAPI 的类型提示和自动生成 OpenAPI 文档太香了,开发效率至少提升 30%。而且 async 原生支持,性能也不差。"
- ),
- "bob": (
- "Go Gin 编译后是原生二进制,内存占用只有 Python 的 1/10。"
- "对于我们这种高并发场景,性能优势明显。"
- "而且 Go 的 goroutine 天然适合并发。"
- ),
- "carol": (
- "从产品角度看,团队 80% 是 Python 背景。"
- "切 Go 需要 3 个月学习周期,这段时间功能迭代会停滞。"
- "FastAPI 能让我们更快交付 MVP。"
- ),
- },
- 2: {
- "alice": (
- "同意 Carol 的观点。而且 FastAPI + Pydantic 的数据校验"
- "几乎是零成本的,Go 里要写大量 struct tag 和 binding 代码。"
- "维护成本 FastAPI 更低。"
- ),
- "bob": (
- "性能不能只看 hello world。FastAPI 在 CPU 密集型任务上"
- "还是有 GIL 瓶颈。不过我承认,如果用 asyncio + uvicorn,"
- "IO 密集场景差距没那么大。可以考虑 FastAPI + 分层架构。"
- ),
- "carol": (
- "Bob 说的分层架构我支持。先用 FastAPI 快速上线,"
- "性能瓶颈模块后续可以用 Go 重写微服务。"
- "这才是务实的技术选型策略。"
- ),
- },
- 3: {
- "alice": (
- "最终方案:FastAPI 作为主力框架,搭配 Celery 处理异步任务。"
- "性能关键路径预留 Go 微服务接口。这样既保证了开发效率,"
- "又不堵死性能优化的路。"
- ),
- "bob": (
- "我同意这个折中方案。但需要在架构设计阶段就定义好"
- "服务边界和 API 契约,避免后面拆分时返工。"
- "建议第一周就定好领域模型。"
- ),
- "carol": (
- "完美!这样我们两周内就能出 MVP。技术风险可控,团队也不需要额外学习成本。我会把这个方案同步给管理层。"
- ),
- },
-}
-DEMO_FINDINGS: dict[int, list[tuple[str, str]]] = {
- 1: [
- ("consensus", "团队熟悉 Python,学习成本是关键考量因素"),
- ("disagreement", "Go 性能优势 vs FastAPI 开发效率,优先级不同"),
- ("new_point", "需要评估 IO 密集 vs CPU 密集的实际占比"),
- ],
- 2: [
- ("consensus", "IO 密集场景下 FastAPI 性能差距可接受"),
- ("consensus", "分层架构是合理的折中方案"),
- ("disagreement", "是否需要在第一阶段就引入 Go 微服务"),
- ],
- 3: [
- ("consensus", "采用 FastAPI 主框架 + 预留 Go 微服务扩展"),
- ("consensus", "第一周完成领域模型和 API 契约设计"),
- ("consensus", "两周内交付 MVP,性能瓶颈模块后续迭代"),
- ],
-}
+logger = logging.getLogger(__name__)
class DemoRunner:
@@ -119,7 +23,7 @@ def run(
participants: list[dict[str, Any]] | None = None,
max_rounds: int = 3,
verbose: bool = True,
- web: bool = False,
+ web: bool = True,
web_port: int = 8199,
stream_delay: float = 0.0,
) -> dict[str, Any]:
@@ -139,34 +43,17 @@ def run(
topic=topic,
participants=participants,
max_rounds=max_rounds,
+ web=web,
+ web_port=web_port,
)
disc_id = result["discussion_id"]
-
- # 1b. Optionally start web viewer
- publisher = None
- if web:
- from roundtable.web_publisher import WebPublisher
-
- output_dir = self.core._get_web_dir(disc_id)
- publisher = WebPublisher(output_dir, port=web_port)
- url = publisher.start(
- disc_id,
- topic=topic,
- participants=[
- {
- "profile": p["profile"],
- "display_name": p.get("display_name", p["profile"]),
- "role": p.get("role", ""),
- "avatar": p.get("avatar", ""),
- "title": p.get("title", ""),
- "description": p.get("description", ""),
- }
- for p in participants
- ],
- )
- self.core._publishers[disc_id] = publisher
- if verbose:
- print(f"\n 🌐 Web viewer: {url}\n")
+ web_url = result.get("web_url")
+ web_status = result.get("web_status")
+ if verbose and web:
+ if web_url:
+ print(f"\n Web viewer: {web_url}\n")
+ else:
+ print(f"\n Web viewer unavailable: {result.get('web_error') or 'unknown error'}\n")
self.core.speak(disc_id, "coordinator", f"开场:围绕「{topic}」展开圆桌讨论。")
@@ -237,7 +124,10 @@ def run(
"consensus_points": summary.get("consensus_points", []),
"disagreement_points": summary.get("disagreement_points", []),
"summary": summary,
- "web_url": publisher.url if publisher else None,
+ "web_url": web_url,
+ "web_status": web_status,
+ "web_error": result.get("web_error"),
+ "web_help": result.get("web_help"),
}
@staticmethod
@@ -318,3 +208,34 @@ def _demo_print_conclusion(conclusion: str, summary: dict[str, Any]) -> None:
print("╰" + "─" * width + "╯")
print()
+
+
+def build_parser() -> argparse.ArgumentParser:
+ parser = argparse.ArgumentParser(description="Run the Roundtable demo discussion")
+ web_group = parser.add_mutually_exclusive_group()
+ web_group.add_argument("--web", dest="web", action="store_true", help="Start the web viewer (default)")
+ web_group.add_argument("--no-web", dest="web", action="store_false", help="Run without the web viewer")
+ parser.set_defaults(web=True)
+ parser.add_argument("--port", type=int, default=8199, help="Preferred web viewer port")
+ parser.add_argument("--rounds", type=int, default=3, help="Number of demo rounds")
+ return parser
+
+
+def main() -> None:
+ from roundtable.core import RoundtableCore
+
+ args = build_parser().parse_args()
+ core = RoundtableCore()
+ result = core.run_demo(max_rounds=args.rounds, web=args.web, web_port=args.port)
+ if result.get("web_url"):
+ print(f"Web viewer: {result['web_url']}")
+ else:
+ print(f"Web status: {result.get('web_status', 'disabled')}")
+ if result.get("web_error"):
+ print(f"Web error: {result['web_error']}")
+ if result.get("web_help"):
+ print(result["web_help"])
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/roundtable/demo_data.py b/src/roundtable/demo_data.py
new file mode 100644
index 0000000..33288d1
--- /dev/null
+++ b/src/roundtable/demo_data.py
@@ -0,0 +1,102 @@
+from __future__ import annotations
+
+from typing import Any
+
+DEMO_TOPIC: str = "选择后端框架:FastAPI vs Go Gin vs Node Express"
+DEMO_PARTICIPANTS: list[dict[str, Any]] = [
+ {
+ "profile": "alice",
+ "role": "全栈工程师",
+ "display_name": "Alice",
+ "perspective": "重视开发效率和生态",
+ "avatar": "👩💻",
+ "title": "Senior Full-Stack Engineer",
+ "description": (
+ "10 年全栈经验,主导过多个从零到一的产品架构。擅长 Python/TypeScript 全栈,关注开发者体验和交付效率。"
+ ),
+ },
+ {
+ "profile": "bob",
+ "role": "架构师",
+ "display_name": "Bob",
+ "perspective": "重视性能和可维护性",
+ "avatar": "🏗️",
+ "title": "Principal Architect",
+ "description": "分布式系统架构专家,专注于高可用和水平扩展方案。擅长 Go/Rust 技术栈,推崇简洁的系统设计哲学。",
+ },
+ {
+ "profile": "carol",
+ "role": "产品经理",
+ "display_name": "Carol",
+ "perspective": "重视交付速度 and 团队学习成本",
+ "avatar": "📊",
+ "title": "Product Director",
+ "description": "5 年 B 端产品经验,关注技术选型对业务交付的影响。擅长平衡技术理想与业务现实,推动敏捷迭代。",
+ },
+]
+DEMO_SPEECHES: dict[int, dict[str, str]] = {
+ 1: {
+ "alice": (
+ "FastAPI 的类型提示和自动生成 OpenAPI 文档太香了,开发效率至少提升 30%。而且 async 原生支持,性能也不差。"
+ ),
+ "bob": (
+ "Go Gin 编译后是原生二进制,内存占用只有 Python 的 1/10。"
+ "对于我们这种高并发场景,性能优势明显。"
+ "而且 Go 的 goroutine 天然适合并发。"
+ ),
+ "carol": (
+ "从产品角度看,团队 80% 是 Python 背景。"
+ "切 Go 需要 3 个月学习周期,这段时间功能迭代会停滞。"
+ "FastAPI 能让我们更快交付 MVP。"
+ ),
+ },
+ 2: {
+ "alice": (
+ "同意 Carol 的观点。而且 FastAPI + Pydantic 的数据校验"
+ "几乎是零成本的,Go 里要写大量 struct tag 和 binding 代码。"
+ "维护成本 FastAPI 更低。"
+ ),
+ "bob": (
+ "性能不能只看 hello world。FastAPI 在 CPU 密集型任务上"
+ "还是有 GIL 瓶颈。不过我承认,如果用 asyncio + uvicorn,"
+ "IO 密集场景差距没那么大。可以考虑 FastAPI + 分层架构。"
+ ),
+ "carol": (
+ "Bob 说的分层架构我支持。先用 FastAPI 快速上线,"
+ "性能瓶颈模块后续可以用 Go 重写微服务。"
+ "这才是务实的技术选型策略。"
+ ),
+ },
+ 3: {
+ "alice": (
+ "最终方案:FastAPI 作为主力框架,搭配 Celery 处理异步任务。"
+ "性能关键路径预留 Go 微服务接口。这样既保证了开发效率,"
+ "又不堵死性能优化的路。"
+ ),
+ "bob": (
+ "我同意这个折中方案。但需要在架构设计阶段就定义好"
+ "服务边界和 API 契约,避免后面拆分时返工。"
+ "建议第一周就定好领域模型。"
+ ),
+ "carol": (
+ "完美!这样我们两周内就能出 MVP。技术风险可控,团队也不需要额外学习成本。我会把这个方案同步给管理层。"
+ ),
+ },
+}
+DEMO_FINDINGS: dict[int, list[tuple[str, str]]] = {
+ 1: [
+ ("consensus", "团队熟悉 Python,学习成本是关键考量因素"),
+ ("disagreement", "Go 性能优势 vs FastAPI 开发效率,优先级不同"),
+ ("new_point", "需要评估 IO 密集 vs CPU 密集的实际占比"),
+ ],
+ 2: [
+ ("consensus", "IO 密集场景下 FastAPI 性能差距可接受"),
+ ("consensus", "分层架构是合理的折中方案"),
+ ("disagreement", "是否需要在第一阶段就引入 Go 微服务"),
+ ],
+ 3: [
+ ("consensus", "采用 FastAPI 主框架 + 预留 Go 微服务扩展"),
+ ("consensus", "第一周完成领域模型和 API 契约设计"),
+ ("consensus", "两周内交付 MVP,性能瓶颈模块后续迭代"),
+ ],
+}
diff --git a/src/roundtable/web/package-lock.json b/src/roundtable/web/package-lock.json
new file mode 100644
index 0000000..544b1a5
--- /dev/null
+++ b/src/roundtable/web/package-lock.json
@@ -0,0 +1,3109 @@
+{
+ "name": "roundtable-web-viewer",
+ "version": "2.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "roundtable-web-viewer",
+ "version": "2.1.0",
+ "dependencies": {
+ "bcryptjs": "^3.0.3",
+ "express": "^5.1.0",
+ "md-to-pdf": "^5.2.4"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@puppeteer/browsers": {
+ "version": "2.13.2",
+ "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.13.2.tgz",
+ "integrity": "sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "extract-zip": "^2.0.1",
+ "progress": "^2.0.3",
+ "proxy-agent": "^6.5.0",
+ "semver": "^7.7.4",
+ "tar-fs": "^3.1.1",
+ "yargs": "^17.7.2"
+ },
+ "bin": {
+ "browsers": "lib/cjs/main-cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@samverschueren/stream-to-observable": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
+ "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==",
+ "license": "MIT",
+ "dependencies": {
+ "any-observable": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependenciesMeta": {
+ "rxjs": {
+ "optional": true
+ },
+ "zen-observable": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tootallnate/quickjs-emscripten": {
+ "version": "0.23.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz",
+ "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==",
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "25.9.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.2.tgz",
+ "integrity": "sha512-G05zqtJhcDLb8uslf5EjCxXg9G1KQxiV8OS0R26IC//Eoyitzqe8z37I7cqvnZlrlSfgocQRfSn/AHBZJJFyGw==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "undici-types": ">=7.24.0 <7.24.7"
+ }
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-observable": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
+ "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "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/ast-types": {
+ "version": "0.13.4",
+ "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz",
+ "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/b4a": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.8.1.tgz",
+ "integrity": "sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react-native-b4a": "*"
+ },
+ "peerDependenciesMeta": {
+ "react-native-b4a": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "license": "MIT"
+ },
+ "node_modules/bare-events": {
+ "version": "2.9.1",
+ "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.9.1.tgz",
+ "integrity": "sha512-Z0oHEHAFDZkffN8Qc39zNZjQlMDkPJRyyyZieU1VH7u8c5S+qHZ2S8ixdKIAxEjfHO7FJxXmJWgteOghVanIsg==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "bare-abort-controller": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-fs": {
+ "version": "4.7.2",
+ "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.7.2.tgz",
+ "integrity": "sha512-aTvMFUWkBmjzKtEQMDGGDNF8bkfpD5N1b/FCwt7A3wrU4t1o/e/85Wzkluh6JlODCjqVESYCkQCdTXqZ9G7VFg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.5.4",
+ "bare-path": "^3.0.0",
+ "bare-stream": "^2.6.4",
+ "bare-url": "^2.2.2",
+ "fast-fifo": "^1.3.2"
+ },
+ "engines": {
+ "bare": ">=1.16.0"
+ },
+ "peerDependencies": {
+ "bare-buffer": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-buffer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-os": {
+ "version": "3.9.1",
+ "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.9.1.tgz",
+ "integrity": "sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "bare": ">=1.14.0"
+ }
+ },
+ "node_modules/bare-path": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.1.tgz",
+ "integrity": "sha512-ghj2DSK/2e99a1anTVPCV4m4YIYtrbXhfM7V3D7XZLOTsybnYyaJloymGqssQc8l/or0UoDyRtNQkmkEF/ysgQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-os": "^3.0.1"
+ }
+ },
+ "node_modules/bare-stream": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.13.1.tgz",
+ "integrity": "sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "streamx": "^2.25.0",
+ "teex": "^1.0.1"
+ },
+ "peerDependencies": {
+ "bare-abort-controller": "*",
+ "bare-buffer": "*",
+ "bare-events": "*"
+ },
+ "peerDependenciesMeta": {
+ "bare-abort-controller": {
+ "optional": true
+ },
+ "bare-buffer": {
+ "optional": true
+ },
+ "bare-events": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/bare-url": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.4.5.tgz",
+ "integrity": "sha512-K+y9xF1tN+CdPu4qWwr0QiK1Al07eFPGYK5M2pDXcmHdMdgC/tT/bpmMe1hrmRHaidKLkXrC+cRNYf3XVDUhSQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/basic-ftp": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.3.1.tgz",
+ "integrity": "sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/bcryptjs": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz",
+ "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==",
+ "license": "BSD-3-Clause",
+ "bin": {
+ "bcrypt": "bin/bcrypt"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/body-parser": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz",
+ "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^1.0.5",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.0",
+ "iconv-lite": "^0.7.0",
+ "on-finished": "^2.4.1",
+ "qs": "^6.14.1",
+ "raw-body": "^3.0.1",
+ "type-is": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chromium-bidi": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz",
+ "integrity": "sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "mitt": "^3.0.1",
+ "zod": "^3.24.1"
+ },
+ "peerDependencies": {
+ "devtools-protocol": "*"
+ }
+ },
+ "node_modules/cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
+ "license": "MIT",
+ "dependencies": {
+ "restore-cursor": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
+ "integrity": "sha512-f4r4yJnbT++qUPI9NR4XLDLq41gQ+uqnPItWG0F5ZkehuNiTTa3EY0S4AqTSUOeJ7/zU41oWPQSNkW5BqPL9bg==",
+ "license": "MIT",
+ "dependencies": {
+ "slice-ansi": "0.0.4",
+ "string-width": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cliui/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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "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/cliui/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==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/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==",
+ "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/code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "license": "MIT"
+ },
+ "node_modules/content-disposition": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz",
+ "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz",
+ "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/cosmiconfig": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz",
+ "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==",
+ "license": "MIT",
+ "dependencies": {
+ "env-paths": "^2.2.1",
+ "import-fresh": "^3.3.0",
+ "js-yaml": "^4.1.0",
+ "parse-json": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/d-fischer"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.9.5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/cosmiconfig/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "license": "Python-2.0"
+ },
+ "node_modules/cosmiconfig/node_modules/js-yaml": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz",
+ "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/puzrin"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/nodeca"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz",
+ "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==",
+ "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/degenerator": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz",
+ "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ast-types": "^0.13.4",
+ "escodegen": "^2.1.0",
+ "esprima": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/devtools-protocol": {
+ "version": "0.0.1608973",
+ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1608973.tgz",
+ "integrity": "sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+ "license": "MIT"
+ },
+ "node_modules/elegant-spinner": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
+ "integrity": "sha512-B+ZM+RXvRqQaAmkMlO/oSe5nMUOaUnyfGYCEHoR8wrXsZR2mA0XVibsxV1bvTwxdRWah1PkQqso2EzhILGHtEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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==",
+ "license": "MIT"
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+ "license": "MIT"
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/escodegen": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz",
+ "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esprima": "^4.0.1",
+ "estraverse": "^5.2.0",
+ "esutils": "^2.0.2"
+ },
+ "bin": {
+ "escodegen": "bin/escodegen.js",
+ "esgenerate": "bin/esgenerate.js"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "optionalDependencies": {
+ "source-map": "~0.6.1"
+ }
+ },
+ "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/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/events-universal": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz",
+ "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "bare-events": "^2.7.0"
+ }
+ },
+ "node_modules/express": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
+ "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
+ "license": "MIT",
+ "dependencies": {
+ "accepts": "^2.0.0",
+ "body-parser": "^2.2.1",
+ "content-disposition": "^1.0.0",
+ "content-type": "^1.0.5",
+ "cookie": "^0.7.1",
+ "cookie-signature": "^1.2.1",
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "finalhandler": "^2.1.0",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.0",
+ "merge-descriptors": "^2.0.0",
+ "mime-types": "^3.0.0",
+ "on-finished": "^2.4.1",
+ "once": "^1.4.0",
+ "parseurl": "^1.3.3",
+ "proxy-addr": "^2.0.7",
+ "qs": "^6.14.0",
+ "range-parser": "^1.2.1",
+ "router": "^2.2.0",
+ "send": "^1.1.0",
+ "serve-static": "^2.2.0",
+ "statuses": "^2.0.1",
+ "type-is": "^2.0.1",
+ "vary": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/fast-fifo": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
+ "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==",
+ "license": "MIT"
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "license": "MIT",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/figures": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+ "integrity": "sha512-UxKlfCRuCBxSXU4C6t9scbDyWZ4VlaFFdojKtzJuSkuOBQ5CNFum+zZXFwHjo+CxBC1t6zlYPgHIgFjL8ggoEQ==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5",
+ "object-assign": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz",
+ "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "on-finished": "^2.4.1",
+ "parseurl": "^1.3.3",
+ "statuses": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 18.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz",
+ "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-port": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
+ "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-stdin": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-8.0.0.tgz",
+ "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/get-uri": {
+ "version": "6.0.5",
+ "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz",
+ "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==",
+ "license": "MIT",
+ "dependencies": {
+ "basic-ftp": "^5.0.2",
+ "data-uri-to-buffer": "^6.0.2",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/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/has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
+ "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/highlight.js": {
+ "version": "11.11.1",
+ "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
+ "integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
+ "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
+ "license": "MIT",
+ "dependencies": {
+ "depd": "~2.0.0",
+ "inherits": "~2.0.4",
+ "setprototypeof": "~1.2.0",
+ "statuses": "~2.0.2",
+ "toidentifier": "~1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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==",
+ "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==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+ "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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/ip-address": {
+ "version": "10.2.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz",
+ "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-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-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==",
+ "license": "MIT",
+ "dependencies": {
+ "number-is-nan": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-observable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz",
+ "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==",
+ "license": "MIT",
+ "dependencies": {
+ "symbol-observable": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "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/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "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/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/listr": {
+ "version": "0.14.3",
+ "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
+ "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==",
+ "license": "MIT",
+ "dependencies": {
+ "@samverschueren/stream-to-observable": "^0.3.0",
+ "is-observable": "^1.1.0",
+ "is-promise": "^2.1.0",
+ "is-stream": "^1.1.0",
+ "listr-silent-renderer": "^1.1.1",
+ "listr-update-renderer": "^0.5.0",
+ "listr-verbose-renderer": "^0.5.0",
+ "p-map": "^2.0.0",
+ "rxjs": "^6.3.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/listr-silent-renderer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
+ "integrity": "sha512-L26cIFm7/oZeSNVhWB6faeorXhMg4HNlb/dS/7jHhr708jxlXrtrBWo4YUxZQkc6dGoxEAe6J/D3juTRBUzjtA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-update-renderer": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz",
+ "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^1.1.3",
+ "cli-truncate": "^0.2.1",
+ "elegant-spinner": "^1.0.1",
+ "figures": "^1.7.0",
+ "indent-string": "^3.0.0",
+ "log-symbols": "^1.0.2",
+ "log-update": "^2.3.0",
+ "strip-ansi": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "peerDependencies": {
+ "listr": "^0.14.2"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/listr-verbose-renderer": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz",
+ "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^2.4.1",
+ "cli-cursor": "^2.1.0",
+ "date-fns": "^1.27.2",
+ "figures": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "license": "MIT"
+ },
+ "node_modules/listr-verbose-renderer/node_modules/figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
+ "integrity": "sha512-mmPrW0Fh2fxOzdBbFv4g1m6pR72haFLPJ2G5SJEELf1y+iaQrDG6cWCPjy54RHYbZAt7X+ls690Kw62AdWXBzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "chalk": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/log-update": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz",
+ "integrity": "sha512-vlP11XfFGyeNQlmEn9tJ66rEW1coA/79m5z6BCkudjbAGE83uhAcGYrBFwfs3AdLiLzGRusRPAbSPK9xZteCmg==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-escapes": "^3.0.0",
+ "cli-cursor": "^2.0.0",
+ "wrap-ansi": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/marked": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz",
+ "integrity": "sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/md-to-pdf": {
+ "version": "5.2.5",
+ "resolved": "https://registry.npmjs.org/md-to-pdf/-/md-to-pdf-5.2.5.tgz",
+ "integrity": "sha512-TG8TgDM0PmEwCldR6j/1QP9gBElLL3DSn5ID8P3bEXEl3Y2zHOUSyszHzabWnDNxklRjKbi40ybli8YQJ5Ym5w==",
+ "license": "MIT",
+ "dependencies": {
+ "arg": "^5.0.2",
+ "chalk": "^4.1.2",
+ "chokidar": "^3.5.2",
+ "get-port": "^5.1.1",
+ "get-stdin": "^8.0.0",
+ "gray-matter": "^4.0.3",
+ "highlight.js": "^11.7.0",
+ "iconv-lite": "^0.6.3",
+ "listr": "^0.14.3",
+ "marked": "^4.2.12",
+ "puppeteer": ">=8.0.0",
+ "semver": "^7.3.7",
+ "serve-handler": "^6.1.3"
+ },
+ "bin": {
+ "md-to-pdf": "dist/cli.js",
+ "md2pdf": "dist/cli.js"
+ },
+ "engines": {
+ "node": ">=12.0"
+ }
+ },
+ "node_modules/md-to-pdf/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/media-typer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz",
+ "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/merge-descriptors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz",
+ "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.54.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz",
+ "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz",
+ "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mitt": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz",
+ "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/negotiator": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz",
+ "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/netmask": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.1.1.tgz",
+ "integrity": "sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "license": "MIT",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "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/onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mimic-fn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pac-proxy-agent": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz",
+ "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tootallnate/quickjs-emscripten": "^0.23.0",
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "get-uri": "^6.0.1",
+ "http-proxy-agent": "^7.0.0",
+ "https-proxy-agent": "^7.0.6",
+ "pac-resolver": "^7.0.1",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/pac-resolver": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz",
+ "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==",
+ "license": "MIT",
+ "dependencies": {
+ "degenerator": "^5.0.0",
+ "netmask": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-is-inside": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+ "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==",
+ "license": "(WTFPL OR MIT)"
+ },
+ "node_modules/path-to-regexp": {
+ "version": "8.4.2",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz",
+ "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "license": "MIT",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/proxy-agent": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz",
+ "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "http-proxy-agent": "^7.0.1",
+ "https-proxy-agent": "^7.0.6",
+ "lru-cache": "^7.14.1",
+ "pac-proxy-agent": "^7.1.0",
+ "proxy-from-env": "^1.1.0",
+ "socks-proxy-agent": "^8.0.5"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
+ "node_modules/pump": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
+ "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==",
+ "license": "MIT",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/puppeteer": {
+ "version": "24.43.1",
+ "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.43.1.tgz",
+ "integrity": "sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.13.2",
+ "chromium-bidi": "14.0.0",
+ "cosmiconfig": "^9.0.0",
+ "devtools-protocol": "0.0.1608973",
+ "puppeteer-core": "24.43.1",
+ "typed-query-selector": "^2.12.2"
+ },
+ "bin": {
+ "puppeteer": "lib/cjs/puppeteer/node/cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/puppeteer-core": {
+ "version": "24.43.1",
+ "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.43.1.tgz",
+ "integrity": "sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@puppeteer/browsers": "2.13.2",
+ "chromium-bidi": "14.0.0",
+ "debug": "^4.4.3",
+ "devtools-protocol": "0.0.1608973",
+ "typed-query-selector": "^2.12.2",
+ "webdriver-bidi-protocol": "0.4.1",
+ "ws": "^8.20.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz",
+ "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz",
+ "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "~3.1.2",
+ "http-errors": "~2.0.1",
+ "iconv-lite": "~0.7.0",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
+ "license": "MIT",
+ "dependencies": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/router": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
+ "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.0",
+ "depd": "^2.0.0",
+ "is-promise": "^4.0.0",
+ "parseurl": "^1.3.3",
+ "path-to-regexp": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ }
+ },
+ "node_modules/router/node_modules/is-promise": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==",
+ "license": "MIT"
+ },
+ "node_modules/rxjs": {
+ "version": "6.6.7",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz",
+ "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^1.9.0"
+ },
+ "engines": {
+ "npm": ">=2.0.0"
+ }
+ },
+ "node_modules/rxjs/node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "license": "0BSD"
+ },
+ "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/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": "7.8.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz",
+ "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==",
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/send": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz",
+ "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.4.3",
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "etag": "^1.8.1",
+ "fresh": "^2.0.0",
+ "http-errors": "^2.0.1",
+ "mime-types": "^3.0.2",
+ "ms": "^2.1.3",
+ "on-finished": "^2.4.1",
+ "range-parser": "^1.2.1",
+ "statuses": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/serve-handler": {
+ "version": "6.1.7",
+ "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.7.tgz",
+ "integrity": "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg==",
+ "license": "MIT",
+ "dependencies": {
+ "bytes": "3.0.0",
+ "content-disposition": "0.5.2",
+ "mime-types": "2.1.18",
+ "minimatch": "3.1.5",
+ "path-is-inside": "1.0.2",
+ "path-to-regexp": "3.3.0",
+ "range-parser": "1.2.0"
+ }
+ },
+ "node_modules/serve-handler/node_modules/bytes": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
+ "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serve-handler/node_modules/content-disposition": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz",
+ "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-db": {
+ "version": "1.33.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
+ "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/mime-types": {
+ "version": "2.1.18",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz",
+ "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "~1.33.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-handler/node_modules/path-to-regexp": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz",
+ "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==",
+ "license": "MIT"
+ },
+ "node_modules/serve-handler/node_modules/range-parser": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz",
+ "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "dependencies": {
+ "encodeurl": "^2.0.0",
+ "escape-html": "^1.0.3",
+ "parseurl": "^1.3.3",
+ "send": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+ "license": "ISC"
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz",
+ "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4",
+ "side-channel-list": "^1.0.1",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz",
+ "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/slice-ansi": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+ "integrity": "sha512-up04hB2hR92PgjpyU3y/eg91yIBILyjVY26NvvciY3EVVPjybkMszMpXQ9QAkcS3I5rtJBDLoTxxg+qvW8c7rw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz",
+ "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==",
+ "license": "MIT",
+ "dependencies": {
+ "ip-address": "^10.1.1",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "8.0.5",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz",
+ "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "^4.3.4",
+ "socks": "^2.8.3"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/streamx": {
+ "version": "2.27.0",
+ "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.27.0.tgz",
+ "integrity": "sha512-WZ189TKnHoAokYHvwzaAQMpd55cgUmFIcJFzBSgGcb886jau5DL+XdDhTWV4ps3FLvk+OORp0dLRTPsLZ21CSA==",
+ "license": "MIT",
+ "dependencies": {
+ "events-universal": "^1.0.0",
+ "fast-fifo": "^1.3.2",
+ "text-decoder": "^1.1.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==",
+ "license": "MIT",
+ "dependencies": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "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/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tar-fs": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz",
+ "integrity": "sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^3.0.0",
+ "tar-stream": "^3.1.5"
+ },
+ "optionalDependencies": {
+ "bare-fs": "^4.0.1",
+ "bare-path": "^3.0.0"
+ }
+ },
+ "node_modules/tar-stream": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.2.0.tgz",
+ "integrity": "sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==",
+ "license": "MIT",
+ "dependencies": {
+ "b4a": "^1.6.4",
+ "bare-fs": "^4.5.5",
+ "fast-fifo": "^1.2.0",
+ "streamx": "^2.15.0"
+ }
+ },
+ "node_modules/teex": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz",
+ "integrity": "sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==",
+ "license": "MIT",
+ "dependencies": {
+ "streamx": "^2.12.5"
+ }
+ },
+ "node_modules/text-decoder": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.7.tgz",
+ "integrity": "sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "b4a": "^1.6.4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-is": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz",
+ "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==",
+ "license": "MIT",
+ "dependencies": {
+ "content-type": "^2.0.0",
+ "media-typer": "^1.1.0",
+ "mime-types": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/type-is/node_modules/content-type": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz",
+ "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/typed-query-selector": {
+ "version": "2.12.2",
+ "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.2.tgz",
+ "integrity": "sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==",
+ "license": "MIT"
+ },
+ "node_modules/undici-types": {
+ "version": "7.24.6",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
+ "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/webdriver-bidi-protocol": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.4.1.tgz",
+ "integrity": "sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz",
+ "integrity": "sha512-iXR3tDXpbnTpzjKSylUJRkLuOrEC7hwEB221cgn6wtF8wpmz28puFXAEfPT5zrjM3wahygB//VuWEr1vTkDcNQ==",
+ "license": "MIT",
+ "dependencies": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz",
+ "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "license": "MIT",
+ "dependencies": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "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.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
+ "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/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "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==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs/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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/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==",
+ "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/yargs/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==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/zod": {
+ "version": "3.25.76",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
+ "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ }
+ }
+}
diff --git a/src/roundtable/web/package.json b/src/roundtable/web/package.json
new file mode 100644
index 0000000..54ea118
--- /dev/null
+++ b/src/roundtable/web/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "roundtable-web-viewer",
+ "version": "2.1.0",
+ "private": true,
+ "description": "Bundled Express server for Roundtable Web Viewer",
+ "type": "module",
+ "scripts": {
+ "start": "node server.mjs",
+ "check": "node --check server.mjs && node --check viewer.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "dependencies": {
+ "bcryptjs": "^3.0.3",
+ "express": "^5.1.0",
+ "md-to-pdf": "^5.2.4"
+ },
+ "overrides": {
+ "puppeteer": "24.43.1"
+ }
+}
diff --git a/src/roundtable/web/server.mjs b/src/roundtable/web/server.mjs
index 1243225..7d9271b 100644
--- a/src/roundtable/web/server.mjs
+++ b/src/roundtable/web/server.mjs
@@ -25,7 +25,7 @@ import {
} from "node:fs";
import * as readline from "node:readline";
import { join, resolve, dirname, basename } from "node:path";
-import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
+import { createHash, createHmac, randomBytes, timingSafeEqual, pbkdf2Sync } from "node:crypto";
// ---------------------------------------------------------------------------
// CLI args
@@ -61,6 +61,7 @@ const SERVER_SECRET = randomBytes(32);
* discussionPath: string,
* tokenStreamPath: string,
* passwordHash: string | null,
+ * ownerSecretHash: string | null,
* expiresAt: number | null,
* revokedTokenHashes: Set,
* tokenHash: string,
@@ -102,6 +103,7 @@ function registerDiscussion(dir, discussionId) {
discussionPath: discPath,
tokenStreamPath: join(dir, "token_stream.jsonl"),
passwordHash: data.password_hash || null,
+ ownerSecretHash: data.owner_secret_hash || null,
expiresAt: data.expires_at ?? null,
revokedTokenHashes: new Set(data.revoked_token_hashes || []),
tokenHash,
@@ -124,6 +126,7 @@ function refreshEntry(entry) {
byTokenHash.set(tokenHash, entry);
}
entry.passwordHash = data.password_hash || null;
+ entry.ownerSecretHash = data.owner_secret_hash || null;
entry.expiresAt = data.expires_at ?? null;
entry.revokedTokenHashes = new Set(data.revoked_token_hashes || []);
return data;
@@ -165,6 +168,55 @@ function readDiscussionFor(entry) {
return data;
}
+function readDiscussionSnapshot(path) {
+ try {
+ if (!existsSync(path)) return null;
+ const before = statSync(path);
+ const raw = readFileSync(path, "utf-8");
+ const data = JSON.parse(raw);
+ const after = statSync(path);
+ if (before.mtimeMs !== after.mtimeMs || before.size !== after.size) return null;
+ return { data, signature: `${after.mtimeMs}:${after.size}` };
+ } catch {
+ return null;
+ }
+}
+
+function writeRevocationHash(entry, incomingHash) {
+ // Python owns full discussion.json merge semantics under discussion.json.lock.
+ // Node's revoke endpoint performs only a targeted revocation merge: re-read the
+ // latest disk snapshot, add this token hash, and retry if the file changes
+ // before the atomic rename.
+ for (let attempt = 0; attempt < 5; attempt++) {
+ const tmpPath = `${entry.discussionPath}.node-revoke-${process.pid}-${Date.now()}-${attempt}.tmp`;
+ const snapshot = readDiscussionSnapshot(entry.discussionPath);
+ if (!snapshot) continue;
+
+ const data = snapshot.data;
+ const revoked = Array.isArray(data.revoked_token_hashes) ? data.revoked_token_hashes : [];
+ const revokedSet = new Set(revoked);
+ revokedSet.add(incomingHash);
+ data.revoked_token_hashes = [...revokedSet];
+ delete data.revoked_tokens;
+ data.updated_at = Math.floor(Date.now() / 1000);
+
+ try {
+ const latest = statSync(entry.discussionPath);
+ const latestSignature = `${latest.mtimeMs}:${latest.size}`;
+ if (latestSignature !== snapshot.signature) continue;
+
+ writeFileSync(tmpPath, JSON.stringify(data, null, 2));
+ renameSync(tmpPath, entry.discussionPath);
+ return data;
+ } catch {
+ try { unlinkSync(tmpPath); } catch { /* ignore cleanup races */ }
+ continue;
+ }
+ }
+
+ throw new Error("Unable to safely persist revocation");
+}
+
// ---------------------------------------------------------------------------
// Token + password validation
// ---------------------------------------------------------------------------
@@ -182,6 +234,39 @@ function _safeStrEqual(a, b) {
return timingSafeEqual(ab, bb);
}
+function verifySecretHash(secret, hash) {
+ if (!secret || !hash) return false;
+ return _safeStrEqual(_hashToken(secret), hash);
+}
+
+function getQuery(req) {
+ return new URL(req.url, `http://${req.headers.host}`).searchParams;
+}
+
+async function readJSONBody(req) {
+ let body = "";
+ for await (const chunk of req) body += chunk;
+ if (!body.trim()) return {};
+ return JSON.parse(body);
+}
+
+function getOwnerSecret(req, body = {}) {
+ const query = getQuery(req);
+ return (
+ req.headers["x-roundtable-owner-secret"]
+ || body.owner_secret
+ || body.ownerSecret
+ || query.get("owner")
+ || ""
+ );
+}
+
+function isOwnerAuthorized(req, entry, data, body = {}) {
+ const ownerHash = data?.owner_secret_hash || entry?.ownerSecretHash || "";
+ if (!ownerHash) return false;
+ return verifySecretHash(getOwnerSecret(req, body), ownerHash);
+}
+
function isTokenValid(token) {
const entry = entryForToken(token);
if (!entry) return false;
@@ -210,6 +295,28 @@ async function loadBcrypt() {
return _bcrypt;
}
+async function verifyPassword(password, storedHash) {
+ if (!storedHash) return true;
+ if (storedHash.startsWith("pbkdf2_sha256$")) {
+ const parts = storedHash.split("$");
+ if (parts.length !== 4) return false;
+ const iterations = Number.parseInt(parts[1], 10);
+ const salt = parts[2];
+ const expectedHex = parts[3];
+ if (!Number.isFinite(iterations) || !salt || !expectedHex) return false;
+ const derived = pbkdf2Sync(password, salt, iterations, expectedHex.length / 2, "sha256").toString("hex");
+ return _safeStrEqual(derived, expectedHex);
+ }
+
+ const bcrypt = await loadBcrypt();
+ if (!bcrypt) {
+ const err = new Error("Password verification unavailable: bcryptjs dependency is missing");
+ err.code = "PASSWORD_VERIFY_UNAVAILABLE";
+ throw err;
+ }
+ return bcrypt.compare(password, storedHash);
+}
+
function _signSession(token) {
return createHmac("sha256", SERVER_SECRET).update(`auth:${token}`).digest("hex");
}
@@ -390,6 +497,7 @@ function safeDiscussion(data) {
delete safe.token_hash;
delete safe.revoked_token_hashes;
delete safe.password_hash;
+ delete safe.owner_secret_hash;
return safe;
}
@@ -627,6 +735,15 @@ const router = new MiniRouter();
// Routes
// ---------------------------------------------------------------------------
+router.get("/healthz", (_req, res) => {
+ sendJSON(res, {
+ ok: true,
+ service: "roundtable-web",
+ data_dir: DATA_DIR,
+ discussions: byDiscussionId.size,
+ });
+});
+
router.get("/share/:token", (req, res, params) => {
const entry = entryForToken(params.token);
if (!entry) return send403(res);
@@ -740,14 +857,18 @@ function renderViewer(req, res, entry, token, opts) {
return sendHTML(res, `Roundtable Web Viewer
${templateName} not found
`);
}
const html = readFileSync(templatePath, "utf-8");
+ const disc = readDiscussionFor(entry);
+ const ownerSecret = getQuery(req).get("owner") || "";
+ const ownerAuthorized = !embed && disc ? verifySecretHash(ownerSecret, disc.owner_secret_hash || entry.ownerSecretHash) : false;
const config = JSON.stringify({
token,
port,
host: "0.0.0.0",
hasPassword: !!entry.passwordHash,
embed: !!embed,
+ ownerSecret: ownerAuthorized ? ownerSecret : "",
+ canRevoke: ownerAuthorized,
});
- const disc = readDiscussionFor(entry);
const ogTitle = disc?.topic ? `圆桌讨论: ${disc.topic}` : "Roundtable 圆桌讨论";
const participantNames = (disc?.participants || []).map(p => p.display_name || p.profile).filter(Boolean).join("、");
const ogDesc = disc?.topic
@@ -792,7 +913,6 @@ router.get("/r/:token", async (req, res, params) => {
if (!entry || !isTokenValid(params.token)) return send403(res);
if (entry.passwordHash) {
- await loadBcrypt();
if (!isAuthenticated(req, entry, params.token)) return sendPasswordPage(res);
}
@@ -808,7 +928,6 @@ router.get("/embed/:token", async (req, res, params) => {
// no good way to surface a password prompt and it leaks UX onto the host page.
// Surface a clean "open in new tab" hint instead.
if (entry.passwordHash) {
- await loadBcrypt();
if (!isAuthenticated(req, entry, params.token)) {
const url = `/r/${encodeURIComponent(params.token)}`;
const html = `
@@ -854,7 +973,6 @@ router.get("/api/:token/data", async (req, res, params) => {
const entry = entryForToken(params.token);
if (!entry || !isTokenValid(params.token)) return send403(res);
if (entry.passwordHash) {
- await loadBcrypt();
if (!isAuthenticated(req, entry, params.token)) return sendJSON(res, { error: "Password required" }, 401);
}
@@ -867,7 +985,6 @@ router.get("/api/:token/events", async (req, res, params) => {
const entry = entryForToken(params.token);
if (!entry || !isTokenValid(params.token)) return send403(res);
if (entry.passwordHash) {
- await loadBcrypt();
if (!isAuthenticated(req, entry, params.token)) return sendJSON(res, { error: "Password required" }, 401);
}
@@ -911,7 +1028,6 @@ router.get("/api/:token/poll", async (req, res, params) => {
const entry = entryForToken(params.token);
if (!entry || !isTokenValid(params.token)) return send403(res);
if (entry.passwordHash) {
- await loadBcrypt();
if (!isAuthenticated(req, entry, params.token)) return sendJSON(res, { error: "Password required" }, 401);
}
@@ -946,23 +1062,29 @@ router.post("/api/:token/share", (req, res, params) => {
sendJSON(res, { ok: true, share_url: `/share/${params.token}` });
});
-router.post("/api/:token/revoke", (req, res, params) => {
+router.post("/api/:token/revoke", async (req, res, params) => {
const entry = entryForToken(params.token);
if (!entry || !isTokenValid(params.token)) return send403(res);
const data = readDiscussionFor(entry);
if (!data) return sendJSON(res, { error: "Discussion not found" }, 404);
- const incomingHash = _hashToken(params.token);
- if (!data.revoked_token_hashes) data.revoked_token_hashes = [];
- if (!data.revoked_token_hashes.includes(incomingHash)) {
- data.revoked_token_hashes.push(incomingHash);
+ let body = {};
+ try {
+ body = await readJSONBody(req);
+ } catch {
+ return sendJSON(res, { error: "Invalid request" }, 400);
+ }
+ if (!isOwnerAuthorized(req, entry, data, body)) {
+ return sendJSON(res, { error: "Owner authorization required" }, 403);
}
- data.updated_at = Math.floor(Date.now() / 1000);
- const tmpPath = entry.discussionPath + ".tmp";
- writeFileSync(tmpPath, JSON.stringify(data, null, 2));
- renameSync(tmpPath, entry.discussionPath);
+ const incomingHash = _hashToken(params.token);
+ try {
+ writeRevocationHash(entry, incomingHash);
+ } catch {
+ return sendJSON(res, { error: "Unable to persist revocation" }, 500);
+ }
entry.revokedTokenHashes.add(incomingHash);
broadcastToSSE(entry.discussionId, "revoked", { revoked: true });
@@ -975,16 +1097,13 @@ router.post("/api/:token/validate-password", async (req, res, params) => {
if (!entry) return send403(res);
if (!entry.passwordHash) return sendJSON(res, { ok: true, noPassword: true });
- const bcrypt = await loadBcrypt();
- if (!bcrypt) return sendJSON(res, { ok: false, error: "Password verification unavailable" }, 500);
-
- let body = "";
- for await (const chunk of req) body += chunk;
+ let body = {};
try {
- const { password } = JSON.parse(body);
+ body = await readJSONBody(req);
+ const { password } = body;
if (!password) return sendJSON(res, { ok: false, error: "Password required" }, 400);
- const match = await bcrypt.compare(password, entry.passwordHash);
+ const match = await verifyPassword(password, entry.passwordHash);
if (match) {
const sig = _signSession(entry.tokenHash);
const cookieAttrs = `Path=/; HttpOnly; SameSite=Lax; Max-Age=${7 * 24 * 3600}`;
@@ -995,7 +1114,18 @@ router.post("/api/:token/validate-password", async (req, res, params) => {
return sendJSON(res, { ok: true });
}
return sendJSON(res, { ok: false, error: "Incorrect password" }, 401);
- } catch {
+ } catch (err) {
+ if (err?.code === "PASSWORD_VERIFY_UNAVAILABLE") {
+ return sendJSON(
+ res,
+ {
+ ok: false,
+ error: "Password verification unavailable",
+ detail: "Install web dependencies with `npm install --omit=dev` to enable bcrypt password checks.",
+ },
+ 503
+ );
+ }
return sendJSON(res, { ok: false, error: "Invalid request" }, 400);
}
});
@@ -1004,7 +1134,6 @@ router.get("/api/:token/export/markdown", async (req, res, params) => {
const entry = entryForToken(params.token);
if (!entry || !isTokenValid(params.token)) return send403(res);
if (entry.passwordHash) {
- await loadBcrypt();
if (!isAuthenticated(req, entry, params.token)) return sendJSON(res, { error: "Password required" }, 401);
}
@@ -1025,7 +1154,6 @@ router.get("/api/:token/export/pdf", async (req, res, params) => {
const entry = entryForToken(params.token);
if (!entry || !isTokenValid(params.token)) return send403(res);
if (entry.passwordHash) {
- await loadBcrypt();
if (!isAuthenticated(req, entry, params.token)) return sendJSON(res, { error: "Password required" }, 401);
}
@@ -1078,12 +1206,28 @@ router.get("/api/:token/export/pdf", async (req, res, params) => {
});
res.end(pdfBuffer);
} else {
- sendJSON(res, { error: "PDF generation failed — output not found" }, 500);
+ sendJSON(
+ res,
+ {
+ error: "PDF export unavailable",
+ detail: "md-to-pdf completed but did not produce a PDF file.",
+ help: "Markdown export is still available. Install web dependencies and Chromium/Puppeteer support to enable PDF export.",
+ },
+ 503
+ );
}
} catch (err) {
if (!clientClosed) {
console.error("[export/pdf] Error:", err.message);
- sendJSON(res, { error: "PDF generation failed", detail: err.message }, 500);
+ sendJSON(
+ res,
+ {
+ error: "PDF export unavailable",
+ detail: err.message,
+ help: "Markdown export is still available. Install web dependencies and Chromium/Puppeteer support to enable PDF export.",
+ },
+ 503
+ );
}
} finally {
try { unlinkSync(tmpMdPath); } catch { /* ignore */ }
diff --git a/src/roundtable/web/viewer.js b/src/roundtable/web/viewer.js
index ac33811..37fb632 100644
--- a/src/roundtable/web/viewer.js
+++ b/src/roundtable/web/viewer.js
@@ -915,6 +915,11 @@
const $revokeModalOverlay = document.getElementById('revokeModalOverlay');
const $revokeCancelBtn = document.getElementById('revokeCancelBtn');
const $revokeConfirmBtn = document.getElementById('revokeConfirmBtn');
+ const canRevoke = Boolean(CONFIG.canRevoke && CONFIG.ownerSecret);
+ if (!canRevoke) {
+ if ($revokeLinkBtn) $revokeLinkBtn.style.display = 'none';
+ if ($sheetRevokeLinkBtn) $sheetRevokeLinkBtn.style.display = 'none';
+ }
// State
let shareLink = '';
@@ -1129,13 +1134,15 @@
revokeModalOpen = false;
}
- $revokeLinkBtn.addEventListener('click', () => {
- openRevokeModal();
- });
+ if (canRevoke) {
+ $revokeLinkBtn.addEventListener('click', () => {
+ openRevokeModal();
+ });
- $sheetRevokeLinkBtn.addEventListener('click', () => {
- openRevokeModal();
- });
+ $sheetRevokeLinkBtn.addEventListener('click', () => {
+ openRevokeModal();
+ });
+ }
$revokeCancelBtn.addEventListener('click', closeRevokeModal);
@@ -1154,6 +1161,11 @@
try {
const resp = await fetch(`${API_BASE}/api/${CONFIG.token}/revoke`, {
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Roundtable-Owner-Secret': CONFIG.ownerSecret,
+ },
+ body: JSON.stringify({ owner_secret: CONFIG.ownerSecret }),
});
if (resp.ok) {
@@ -1168,7 +1180,7 @@
} catch {
alert(t('networkError'));
$revokeConfirmBtn.disabled = false;
- $revokeConfirmBtn.textContent = '确认撤销';
+ $revokeConfirmBtn.textContent = t('confirmRevoke');
}
});
diff --git a/src/roundtable/web_publisher.py b/src/roundtable/web_publisher.py
index 175fea4..f0a81a2 100644
--- a/src/roundtable/web_publisher.py
+++ b/src/roundtable/web_publisher.py
@@ -1,25 +1,27 @@
"""WebPublisher — manages a live web viewer for a roundtable discussion.
-Uses PM2 to manage the Express subprocess, fcntl for atomic file locking,
-and nanoid for token generation. Discussion data flows one-way through
-a JSON file that Express reads via shared lock + fs.watch.
+Starts a shared local Node.js web server when possible, writes discussion
+state to JSON for the server to read, and keeps the Python discussion flow
+independent from web viewer startup.
"""
from __future__ import annotations
import contextlib
-import fcntl
import hashlib
import json
import logging
import os
import secrets
+import shutil
import socket
import subprocess
import tempfile
import time
from pathlib import Path
from typing import Any
+from urllib import error as url_error
+from urllib import request as url_request
from roundtable.web_helpers import (
get_avatar_for_participant,
@@ -31,6 +33,44 @@
SHARED_PM2_NAME = "roundtable-web"
SHARED_DATA_DIR = Path(tempfile.gettempdir()) / "roundtable_web"
+MIN_NODE_MAJOR = 18
+WEB_HELP = (
+ "Roundtable created the discussion but could not start the web viewer. "
+ "Install Node.js 18+ and run `npm install --omit=dev` in the project if automatic setup failed."
+)
+_DIRECT_SERVER_PROCESSES: list[Any] = []
+
+try:
+ import fcntl
+except ImportError: # pragma: no cover - exercised on Windows
+ fcntl = None # type: ignore[assignment]
+
+
+class _FileLock:
+ """Best-effort advisory file lock with a no-op fallback on platforms without fcntl."""
+
+ def __init__(self, file_obj: Any, mode: int) -> None:
+ self._file_obj = file_obj
+ self._mode = mode
+
+ def __enter__(self) -> None:
+ if fcntl is not None:
+ fcntl.flock(self._file_obj.fileno(), self._mode)
+
+ def __exit__(self, *_exc: object) -> None:
+ if fcntl is not None:
+ fcntl.flock(self._file_obj.fileno(), fcntl.LOCK_UN)
+
+
+def _lock_ex(file_obj: Any) -> _FileLock:
+ mode = fcntl.LOCK_EX if fcntl is not None else 0
+ return _FileLock(file_obj, mode)
+
+
+def _lock_sh(file_obj: Any) -> _FileLock:
+ mode = fcntl.LOCK_SH if fcntl is not None else 0
+ return _FileLock(file_obj, mode)
+
# ---------------------------------------------------------------------------
# Token generation
@@ -52,6 +92,31 @@ def _hash_token(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
+def _hash_password(password: str) -> str:
+ """Return a Node-verifiable PBKDF2 password hash."""
+ salt = secrets.token_urlsafe(18)
+ iterations = 260_000
+ digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt.encode("utf-8"), iterations)
+ return f"pbkdf2_sha256${iterations}${salt}${digest.hex()}"
+
+
+def _safe_tail(text: str, limit: int = 500) -> str:
+ text = text.strip()
+ if len(text) <= limit:
+ return text
+ return text[-limit:]
+
+
+def _parse_node_major(version: str) -> int | None:
+ cleaned = version.strip()
+ if cleaned.startswith("v"):
+ cleaned = cleaned[1:]
+ major = cleaned.split(".", 1)[0]
+ if not major.isdigit():
+ return None
+ return int(major)
+
+
# ---------------------------------------------------------------------------
# WebPublisher
# ---------------------------------------------------------------------------
@@ -93,12 +158,10 @@ def __init__(
self._host = host
self._url_host = "127.0.0.1" if host in {"", "0.0.0.0", "::"} else host
self._password = password
- self._password_hash: str | None = None
- if password:
- import bcrypt # type: ignore[import-not-found,unused-ignore]
-
- self._password_hash = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
+ self._password_hash: str | None = _hash_password(password) if password else None
self._token: str | None = None
+ self._owner_secret: str | None = None
+ self._owner_secret_hash: str | None = None
self._discussion_id: str | None = None
self._revoked: bool = False
self._speeches: list[dict[str, Any]] = []
@@ -130,11 +193,13 @@ def start(
1. Generate token (nanoid 21) if not provided
2. Write initial discussion.json (includes password_hash if set)
- 3. Ensure the shared Express PM2 process is running on the configured port
- 4. Return URL: ``http://:/r/``
+ 3. Ensure the shared local Node.js web server is running
+ 4. Return owner URL: ``http://:/r/?owner=``
"""
self._discussion_id = discussion_id
self._token = token or _generate_token()
+ self._owner_secret = _generate_token(32)
+ self._owner_secret_hash = _hash_token(self._owner_secret)
self._topic = topic or f"Discussion {discussion_id}"
self._participants = participants or []
self._status = status
@@ -364,7 +429,10 @@ def stop(self) -> None:
def url(self) -> str | None:
"""Current web page URL, or None if not started."""
if self._actual_port and self._token:
- return f"http://{self._url_host}:{self._actual_port}/r/{self._token}"
+ url = f"http://{self._url_host}:{self._actual_port}/r/{self._token}"
+ if self._owner_secret:
+ return f"{url}?owner={self._owner_secret}"
+ return url
return None
@property
@@ -377,50 +445,88 @@ def token(self) -> str | None:
"""The access token."""
return self._token
+ @property
+ def owner_secret(self) -> str | None:
+ """Secret required for owner-only operations such as link revocation."""
+ return self._owner_secret
+
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------
def _ensure_shared_server_running(self) -> None:
- """Start the shared PM2 process on demand. No-op if already online."""
- if self._is_shared_running():
- self._wait_for_port(timeout=3.0)
- return
-
+ """Start or reuse a shared local web server on a usable port."""
server_path = Path(__file__).parent / "web" / "server.mjs"
if not server_path.exists():
- raise FileNotFoundError(f"Express server not found: {server_path}")
+ raise FileNotFoundError(f"Roundtable web server not found: {server_path}")
SHARED_DATA_DIR.mkdir(parents=True, exist_ok=True)
+ self._actual_port = self._select_port(self._port)
- cmd = [
- "pm2",
- "start",
- str(server_path),
- "--name",
- SHARED_PM2_NAME,
- "--interpreter",
- "node",
- "--",
- "--port",
- str(self._port),
- "--data-dir",
- str(SHARED_DATA_DIR),
- ]
+ if self._is_roundtable_server_ready(self._actual_port):
+ return
- logger.info("Starting shared PM2: %s", " ".join(cmd))
- result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
+ if self._is_shared_running() and self._wait_for_port(timeout=3.0):
+ return
- if result.returncode != 0:
- if self._is_shared_running():
- self._wait_for_port(timeout=10.0)
+ node = shutil.which("node")
+ if not node:
+ raise RuntimeError("Node.js 18+ is required for the web viewer but `node` was not found on PATH.")
+ self._ensure_supported_node(node)
+
+ attempts: list[str] = []
+ if self._start_direct_node_server(node, server_path):
+ return
+ attempts.append("direct `node server.mjs` did not become ready")
+
+ install_result = self._install_web_dependencies()
+ if install_result is not None:
+ attempts.append(install_result)
+ if self._start_direct_node_server(node, server_path):
return
- raise RuntimeError(f"PM2 start failed (exit {result.returncode}): {result.stderr}")
- self._wait_for_port(timeout=10.0)
+ pm2_result = self._start_pm2_server(node, server_path)
+ if pm2_result is None:
+ return
+ attempts.append(pm2_result)
+
+ detail = "; ".join(a for a in attempts if a)
+ raise RuntimeError(f"Could not start Roundtable web viewer on port {self._actual_port}. {detail}")
+
+ def _ensure_supported_node(self, node: str) -> None:
+ try:
+ result = subprocess.run(
+ [node, "--version"],
+ capture_output=True,
+ text=True,
+ timeout=5,
+ )
+ except (OSError, subprocess.TimeoutExpired) as exc:
+ raise RuntimeError(f"Could not check Node.js version for the web viewer: {exc}") from exc
+
+ version = (result.stdout or result.stderr).strip()
+ major = _parse_node_major(version)
+ if result.returncode != 0 or major is None:
+ raise RuntimeError(f"Could not check Node.js version for the web viewer: {version or 'unknown error'}")
+ if major < MIN_NODE_MAJOR:
+ raise RuntimeError(
+ f"Node.js {MIN_NODE_MAJOR}+ is required for the web viewer; found {version}. "
+ "Install a current Node.js release or run with web=False."
+ )
+
+ def _select_port(self, preferred: int) -> int:
+ """Return a reusable Roundtable port or the next free local TCP port."""
+ for candidate in range(preferred, preferred + 50):
+ if self._is_roundtable_server_ready(candidate):
+ return candidate
+ if not self._is_port_open(candidate):
+ return candidate
+ raise RuntimeError(f"No free port found in range {preferred}-{preferred + 49}")
def _is_shared_running(self) -> bool:
"""Check whether the shared PM2 process is online."""
+ if shutil.which("pm2") is None:
+ return False
try:
result = subprocess.run(
["pm2", "jlist"],
@@ -439,19 +545,155 @@ def _is_shared_running(self) -> bool:
return False
return False
- def _wait_for_port(self, timeout: float = 10.0) -> None:
+ def _wait_for_port(self, timeout: float = 10.0) -> bool:
"""Block until the shared server accepts TCP connections on self._port."""
deadline = time.time() + timeout
while time.time() < deadline:
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- try:
- s.settimeout(0.5)
- s.connect(("127.0.0.1", self._port))
- logger.info("Shared web server ready on port %d", self._port)
- return
- except OSError:
- time.sleep(0.25)
- logger.warning("Shared web server not reachable on port %d after %.1fs", self._port, timeout)
+ if self._is_roundtable_server_ready(self._actual_port):
+ logger.info("Shared web server ready on port %d", self._actual_port)
+ return True
+ time.sleep(0.25)
+ logger.warning("Shared web server not reachable on port %d after %.1fs", self._actual_port, timeout)
+ return False
+
+ def _is_port_open(self, port: int) -> bool:
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
+ try:
+ s.settimeout(0.3)
+ s.connect(("127.0.0.1", port))
+ return True
+ except OSError:
+ return False
+
+ def _is_roundtable_server_ready(self, port: int) -> bool:
+ try:
+ with url_request.urlopen(f"http://127.0.0.1:{port}/healthz", timeout=0.5) as response:
+ if response.status != 200:
+ return False
+ payload = json.loads(response.read().decode("utf-8"))
+ data_dir = Path(str(payload.get("data_dir", ""))).resolve()
+ return bool(payload.get("ok")) and data_dir == SHARED_DATA_DIR.resolve()
+ except (OSError, TimeoutError, url_error.URLError, json.JSONDecodeError, ValueError):
+ return False
+
+ def _start_direct_node_server(self, node: str, server_path: Path) -> bool:
+ cmd = [
+ node,
+ str(server_path),
+ "--port",
+ str(self._actual_port),
+ "--data-dir",
+ str(SHARED_DATA_DIR),
+ ]
+ log_path = SHARED_DATA_DIR / f"server-{self._actual_port}.log"
+ logger.info("Starting Roundtable web server: %s", " ".join(cmd))
+ log_file = None
+ try:
+ log_file = open(log_path, "ab") # noqa: SIM115 - kept open while the child process owns it.
+ popen_kwargs: dict[str, Any] = {
+ "stdout": log_file,
+ "stderr": log_file,
+ "stdin": subprocess.DEVNULL,
+ }
+ if os.name == "nt":
+ popen_kwargs["creationflags"] = getattr(subprocess, "CREATE_NEW_PROCESS_GROUP", 0)
+ else:
+ popen_kwargs["start_new_session"] = True
+ proc = subprocess.Popen(cmd, **popen_kwargs)
+ _DIRECT_SERVER_PROCESSES.append((proc, log_file))
+ except OSError as exc:
+ if log_file is not None:
+ with contextlib.suppress(OSError):
+ log_file.close()
+ logger.warning("Direct Node web server start failed: %s", exc)
+ return False
+
+ if self._wait_for_port(timeout=8.0):
+ return True
+
+ if proc.poll() is not None:
+ logger.warning(
+ "Roundtable web server exited with code %s. Log tail: %s",
+ proc.returncode,
+ self._read_server_log_tail(log_path),
+ )
+ return False
+
+ def _read_server_log_tail(self, log_path: Path) -> str:
+ try:
+ return _safe_tail(log_path.read_text(encoding="utf-8", errors="replace"))
+ except OSError:
+ return ""
+
+ def _find_npm_project_dir(self) -> Path | None:
+ candidates = [
+ Path(__file__).resolve().parent / "web",
+ Path(__file__).resolve().parents[2],
+ ]
+ for candidate in candidates:
+ if (candidate / "package.json").exists():
+ return candidate
+ return None
+
+ def _install_web_dependencies(self) -> str | None:
+ npm = shutil.which("npm")
+ project_dir = self._find_npm_project_dir()
+ if project_dir is None:
+ return "no local package.json found for npm dependency installation"
+ if npm is None:
+ return "npm was not found on PATH, so web dependencies could not be installed automatically"
+
+ cmd = [npm, "install", "--omit=dev"]
+ logger.info("Installing Roundtable web dependencies in %s: %s", project_dir, " ".join(cmd))
+ env = os.environ.copy()
+ env.setdefault("PUPPETEER_SKIP_DOWNLOAD", "true")
+ try:
+ result = subprocess.run(
+ cmd,
+ cwd=project_dir,
+ env=env,
+ capture_output=True,
+ text=True,
+ timeout=120,
+ )
+ except (OSError, subprocess.TimeoutExpired) as exc:
+ return f"`npm install --omit=dev` failed: {exc}"
+
+ if result.returncode != 0:
+ stderr = _safe_tail(result.stderr or result.stdout)
+ return f"`npm install --omit=dev` exited {result.returncode}: {stderr}"
+ return "`npm install --omit=dev` completed"
+
+ def _start_pm2_server(self, node: str, server_path: Path) -> str | None:
+ pm2 = shutil.which("pm2")
+ if pm2 is None:
+ return "PM2 is not installed; skipped optional PM2 fallback"
+
+ cmd = [
+ pm2,
+ "start",
+ str(server_path),
+ "--name",
+ SHARED_PM2_NAME,
+ "--interpreter",
+ node,
+ "--",
+ "--port",
+ str(self._actual_port),
+ "--data-dir",
+ str(SHARED_DATA_DIR),
+ ]
+
+ logger.info("Starting shared PM2 web server: %s", " ".join(cmd))
+ try:
+ result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
+ except (OSError, subprocess.TimeoutExpired) as exc:
+ return f"PM2 fallback failed: {exc}"
+
+ if result.returncode == 0 and self._wait_for_port(timeout=10.0):
+ return None
+ stderr = _safe_tail(result.stderr or result.stdout)
+ return f"PM2 fallback exited {result.returncode}: {stderr}"
def _write_discussion_json(self) -> None:
"""Write current state to discussion.json with atomic file lock."""
@@ -463,6 +705,7 @@ def _write_discussion_json(self) -> None:
"status": self._status,
"token_hash": token_hash,
"password_hash": self._password_hash,
+ "owner_secret_hash": self._owner_secret_hash,
"participants": self._participants,
"speeches": self._speeches,
"round_summaries": self._round_summaries,
@@ -525,15 +768,11 @@ def _append_stream_event(
def _append_token_stream_jsonl(self, event: dict[str, Any]) -> None:
"""Append one event to token_stream.jsonl for SSE tailing/replay."""
target = self._discussion_dir / "token_stream.jsonl"
- with open(target, "a") as f:
- fcntl.flock(f.fileno(), fcntl.LOCK_EX)
- try:
- f.write(json.dumps(event, ensure_ascii=False, separators=(",", ":")))
- f.write("\n")
- f.flush()
- os.fsync(f.fileno())
- finally:
- fcntl.flock(f.fileno(), fcntl.LOCK_UN)
+ with open(target, "a") as f, _lock_ex(f):
+ f.write(json.dumps(event, ensure_ascii=False, separators=(",", ":")))
+ f.write("\n")
+ f.flush()
+ os.fsync(f.fileno())
def _write_discussion_json_raw(self, data: dict[str, Any]) -> None:
"""Atomic write: flock lock file → read/merge existing disk state → write .tmp → fsync → rename."""
@@ -541,188 +780,186 @@ def _write_discussion_json_raw(self, data: dict[str, Any]) -> None:
lock_path = target.with_suffix(".json.lock")
tmp = target.with_suffix(".json.tmp")
- with open(lock_path, "a") as lock_file:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
- try:
- # Read existing data to merge and prevent overwriting cross-process changes
- existing = None
- if target.exists():
- try:
- with open(target, encoding="utf-8") as f:
- existing = json.load(f)
- except (json.JSONDecodeError, FileNotFoundError):
- pass
-
- if existing:
- # 1. Merge speeches by id
- existing_speeches = existing.get("speeches", [])
- speech_map = {s["id"]: s for s in existing_speeches}
- for s in data.get("speeches", []):
- speech_map[s["id"]] = s
- data["speeches"] = sorted(speech_map.values(), key=lambda s: s["id"])
- self._speeches = data["speeches"]
-
- # 2. Merge round_summaries by round
- existing_summaries = existing.get("round_summaries", [])
- summary_map = {s["round"]: s for s in existing_summaries if "round" in s}
- for s in data.get("round_summaries", []):
- r_num = s.get("round")
- if r_num is not None:
- if r_num in summary_map:
- existing_s = summary_map[r_num]
- existing_ts = existing_s.get("timestamp", 0.0)
- data_ts = s.get("timestamp", 0.0)
- if data_ts < existing_ts:
- # Existing on disk is newer; keep it.
+ with open(lock_path, "a") as lock_file, _lock_ex(lock_file):
+ # Read existing data to merge and prevent overwriting cross-process changes
+ existing = None
+ if target.exists():
+ try:
+ with open(target, encoding="utf-8") as f:
+ existing = json.load(f)
+ except (json.JSONDecodeError, FileNotFoundError):
+ pass
+
+ if existing:
+ # 1. Merge speeches by id
+ existing_speeches = existing.get("speeches", [])
+ speech_map = {s["id"]: s for s in existing_speeches}
+ for s in data.get("speeches", []):
+ speech_map[s["id"]] = s
+ data["speeches"] = sorted(speech_map.values(), key=lambda s: s["id"])
+ self._speeches = data["speeches"]
+
+ # 2. Merge round_summaries by round
+ existing_summaries = existing.get("round_summaries", [])
+ summary_map = {s["round"]: s for s in existing_summaries if "round" in s}
+ for s in data.get("round_summaries", []):
+ r_num = s.get("round")
+ if r_num is not None:
+ if r_num in summary_map:
+ existing_s = summary_map[r_num]
+ existing_ts = existing_s.get("timestamp", 0.0)
+ data_ts = s.get("timestamp", 0.0)
+ if data_ts < existing_ts:
+ # Existing on disk is newer; keep it.
+ pass
+ elif data_ts > existing_ts:
+ # Live memory version is newer; use it.
+ summary_map[r_num] = s
+ else:
+ # Timestamps are equal, resolve by completeness
+ ex_has_score = "convergence_score" in existing_s
+ new_has_score = "convergence_score" in s
+ if ex_has_score and not new_has_score:
+ # Existing has score, keep existing
pass
- elif data_ts > existing_ts:
- # Live memory version is newer; use it.
+ elif not ex_has_score and new_has_score:
summary_map[r_num] = s
else:
- # Timestamps are equal, resolve by completeness
- ex_has_score = "convergence_score" in existing_s
- new_has_score = "convergence_score" in s
- if ex_has_score and not new_has_score:
- # Existing has score, keep existing
- pass
- elif not ex_has_score and new_has_score:
+ # Compare information quantity (consensus + disagreement items count)
+ ex_items = len(existing_s.get("consensus", [])) + len(
+ existing_s.get("disagreement", [])
+ )
+ new_items = len(s.get("consensus", [])) + len(s.get("disagreement", []))
+ if new_items > ex_items:
summary_map[r_num] = s
- else:
- # Compare information quantity (consensus + disagreement items count)
- ex_items = len(existing_s.get("consensus", [])) + len(
- existing_s.get("disagreement", [])
- )
- new_items = len(s.get("consensus", [])) + len(s.get("disagreement", []))
- if new_items > ex_items:
- summary_map[r_num] = s
- else:
- summary_map[r_num] = s
- data["round_summaries"] = sorted(summary_map.values(), key=lambda s: s.get("round", 0))
- self._round_summaries = data["round_summaries"]
-
- # 3. Merge final_summary
- existing_fs = existing.get("final_summary")
- data_fs = data.get("final_summary")
- existing_ts = existing_fs.get("timestamp", 0) if existing_fs else 0
- data_ts = data_fs.get("timestamp", 0) if data_fs else 0
-
- if existing_fs and not data_fs:
+ else:
+ summary_map[r_num] = s
+ data["round_summaries"] = sorted(summary_map.values(), key=lambda s: s.get("round", 0))
+ self._round_summaries = data["round_summaries"]
+
+ # 3. Merge final_summary
+ existing_fs = existing.get("final_summary")
+ data_fs = data.get("final_summary")
+ existing_ts = existing_fs.get("timestamp", 0) if existing_fs else 0
+ data_ts = data_fs.get("timestamp", 0) if data_fs else 0
+
+ if existing_fs and not data_fs:
+ data["final_summary"] = existing_fs
+ self._final_summary = existing_fs
+ elif existing_fs and data_fs:
+ if data_ts < existing_ts:
data["final_summary"] = existing_fs
self._final_summary = existing_fs
- elif existing_fs and data_fs:
- if data_ts < existing_ts:
+ elif data_ts > existing_ts:
+ self._final_summary = data_fs
+ else:
+ # Equal timestamps, merge based on completeness/verdict
+ ex_verdict = existing_fs.get("verdict", "")
+ new_verdict = data_fs.get("verdict", "")
+ ex_items = len(existing_fs.get("consensus", [])) + len(existing_fs.get("disagreement", []))
+ new_items = len(data_fs.get("consensus", [])) + len(data_fs.get("disagreement", []))
+ verdict_changed = new_verdict != ex_verdict
+ has_more_items = new_items > ex_items
+ if not verdict_changed and not has_more_items:
data["final_summary"] = existing_fs
self._final_summary = existing_fs
- elif data_ts > existing_ts:
- self._final_summary = data_fs
else:
- # Equal timestamps, merge based on completeness/verdict
- ex_verdict = existing_fs.get("verdict", "")
- new_verdict = data_fs.get("verdict", "")
- ex_items = len(existing_fs.get("consensus", [])) + len(existing_fs.get("disagreement", []))
- new_items = len(data_fs.get("consensus", [])) + len(data_fs.get("disagreement", []))
- verdict_changed = new_verdict != ex_verdict
- has_more_items = new_items > ex_items
- if not verdict_changed and not has_more_items:
- data["final_summary"] = existing_fs
- self._final_summary = existing_fs
- else:
- self._final_summary = data_fs
-
- # 4. Merge status & conclusion
- if existing.get("status") == "concluded" and data.get("status") != "concluded":
- data["status"] = "concluded"
- self._status = "concluded"
- elif existing.get("status") == "active" and data.get("status") == "assembling":
- data["status"] = "active"
- self._status = "active"
- elif existing.get("status") == "cancelled" and data.get("status") in {"assembling", "active"}:
- data["status"] = "cancelled"
- self._status = "cancelled"
-
- existing_conclusion = existing.get("conclusion")
- new_conclusion = data.get("conclusion")
- if existing_conclusion:
- if not new_conclusion:
+ self._final_summary = data_fs
+
+ # 4. Merge status & conclusion
+ if existing.get("status") == "concluded" and data.get("status") != "concluded":
+ data["status"] = "concluded"
+ self._status = "concluded"
+ elif existing.get("status") == "active" and data.get("status") == "assembling":
+ data["status"] = "active"
+ self._status = "active"
+ elif existing.get("status") == "cancelled" and data.get("status") in {"assembling", "active"}:
+ data["status"] = "cancelled"
+ self._status = "cancelled"
+
+ existing_conclusion = existing.get("conclusion")
+ new_conclusion = data.get("conclusion")
+ if existing_conclusion:
+ if not new_conclusion:
+ data["conclusion"] = existing_conclusion
+ self._conclusion = existing_conclusion
+ elif new_conclusion != existing_conclusion:
+ if data_ts < existing_ts:
data["conclusion"] = existing_conclusion
self._conclusion = existing_conclusion
- elif new_conclusion != existing_conclusion:
- if data_ts < existing_ts:
- data["conclusion"] = existing_conclusion
- self._conclusion = existing_conclusion
- else:
- self._conclusion = new_conclusion
-
- # 5. Merge root events (e.g. speech_delta, status_delta from fallback sync)
- existing_events = existing.get("events", [])
- new_events = data.get("events", [])
- if existing_events:
- seen_events = set()
- merged_events = []
-
- def get_event_sig(ev: dict[str, Any]) -> Any:
- ev_type = ev.get("type")
- if ev_type == "speech_delta":
- return ("speech_delta", ev.get("speech", {}).get("id"))
- elif ev_type == "status_delta":
- return ("status_delta", ev.get("status"), ev.get("conclusion"))
- elif ev_type == "round_summary":
- return ("round_summary", ev.get("round"))
- elif ev_type == "final_summary":
- return ("final_summary", ev.get("verdict"))
- else:
- return json.dumps(ev, sort_keys=True)
-
- for ev in existing_events + new_events:
- sig = get_event_sig(ev)
- if sig not in seen_events:
- seen_events.add(sig)
- merged_events.append(ev)
- data["events"] = merged_events
-
- existing_revoked = list(existing.get("revoked_token_hashes", []))
- for old_token in existing.get("revoked_tokens", []):
- existing_revoked.append(_hash_token(old_token))
- new_revoked = data.get("revoked_token_hashes", [])
- merged_revoked = list(set(existing_revoked + new_revoked))
- data["revoked_token_hashes"] = merged_revoked
- data.pop("revoked_tokens", None)
- if self._token and _hash_token(self._token) in merged_revoked:
- self._revoked = True
-
- # 7. Preserve password_hash from disk if memory doesn't have one
- if existing.get("password_hash") and not data.get("password_hash"):
- data["password_hash"] = existing["password_hash"]
-
- # 8. Preserve cross-process dispatch snapshots and joiners.
- # The in-memory publisher may have been created before summoned
- # agents accepted. Avoid reverting DB-backed sync fields.
- for key in ("dispatches", "dispatch_summary"):
- if key in existing and key not in data:
- data[key] = existing[key]
-
- existing_participants = existing.get("participants", [])
- new_participants = data.get("participants", [])
- if existing_participants:
- participant_map = {
- p.get("profile") or p.get("participant"): p
- for p in existing_participants
- if p.get("profile") or p.get("participant")
- }
- for p in new_participants:
- key = p.get("profile") or p.get("participant")
- if key:
- participant_map[key] = p
- data["participants"] = list(participant_map.values())
- self._participants = data["participants"]
-
- with open(tmp, "w", encoding="utf-8") as f:
- json.dump(data, f, ensure_ascii=False, indent=2)
- f.flush()
- os.fsync(f.fileno())
- os.rename(str(tmp), str(target))
- finally:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
+ else:
+ self._conclusion = new_conclusion
+
+ # 5. Merge root events (e.g. speech_delta, status_delta from fallback sync)
+ existing_events = existing.get("events", [])
+ new_events = data.get("events", [])
+ if existing_events:
+ seen_events = set()
+ merged_events = []
+
+ def get_event_sig(ev: dict[str, Any]) -> Any:
+ ev_type = ev.get("type")
+ if ev_type == "speech_delta":
+ return ("speech_delta", ev.get("speech", {}).get("id"))
+ elif ev_type == "status_delta":
+ return ("status_delta", ev.get("status"), ev.get("conclusion"))
+ elif ev_type == "round_summary":
+ return ("round_summary", ev.get("round"))
+ elif ev_type == "final_summary":
+ return ("final_summary", ev.get("verdict"))
+ else:
+ return json.dumps(ev, sort_keys=True)
+
+ for ev in existing_events + new_events:
+ sig = get_event_sig(ev)
+ if sig not in seen_events:
+ seen_events.add(sig)
+ merged_events.append(ev)
+ data["events"] = merged_events
+
+ existing_revoked = list(existing.get("revoked_token_hashes", []))
+ for old_token in existing.get("revoked_tokens", []):
+ existing_revoked.append(_hash_token(old_token))
+ new_revoked = data.get("revoked_token_hashes", [])
+ merged_revoked = list(set(existing_revoked + new_revoked))
+ data["revoked_token_hashes"] = merged_revoked
+ data.pop("revoked_tokens", None)
+ if self._token and _hash_token(self._token) in merged_revoked:
+ self._revoked = True
+
+ # 7. Preserve password_hash from disk if memory doesn't have one
+ if existing.get("password_hash") and not data.get("password_hash"):
+ data["password_hash"] = existing["password_hash"]
+ if existing.get("owner_secret_hash") and not data.get("owner_secret_hash"):
+ data["owner_secret_hash"] = existing["owner_secret_hash"]
+
+ # 8. Preserve cross-process dispatch snapshots and joiners.
+ # The in-memory publisher may have been created before summoned
+ # agents accepted. Avoid reverting DB-backed sync fields.
+ for key in ("dispatches", "dispatch_summary"):
+ if key in existing and key not in data:
+ data[key] = existing[key]
+
+ existing_participants = existing.get("participants", [])
+ new_participants = data.get("participants", [])
+ if existing_participants:
+ participant_map = {
+ p.get("profile") or p.get("participant"): p
+ for p in existing_participants
+ if p.get("profile") or p.get("participant")
+ }
+ for p in new_participants:
+ key = p.get("profile") or p.get("participant")
+ if key:
+ participant_map[key] = p
+ data["participants"] = list(participant_map.values())
+ self._participants = data["participants"]
+
+ with open(tmp, "w", encoding="utf-8") as f:
+ json.dump(data, f, ensure_ascii=False, indent=2)
+ f.flush()
+ os.fsync(f.fileno())
+ os.rename(str(tmp), str(target))
def _read_discussion_json(self) -> dict[str, Any] | None:
"""Read discussion.json with shared lock on lock file."""
@@ -731,13 +968,11 @@ def _read_discussion_json(self) -> dict[str, Any] | None:
return None
lock_path = target.with_suffix(".json.lock")
- with open(lock_path, "a") as lock_file:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_SH)
+ with open(lock_path, "a") as lock_file, _lock_sh(lock_file):
try:
with open(target) as f:
result: dict[str, Any] | None = json.load(f)
return result
except (json.JSONDecodeError, FileNotFoundError):
return None
- finally:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
+ return None
diff --git a/src/roundtable/web_sync.py b/src/roundtable/web_sync.py
index af2205d..55cb35e 100644
--- a/src/roundtable/web_sync.py
+++ b/src/roundtable/web_sync.py
@@ -10,20 +10,34 @@
from __future__ import annotations
-import fcntl
import json
import logging
import os
import sqlite3
import time
from pathlib import Path
-from typing import Any
+from typing import IO, Any
+
+try:
+ import fcntl
+except ImportError: # pragma: no cover - exercised through import smoke tests
+ fcntl = None # type: ignore[assignment]
from roundtable.db import RoundtableDB
logger = logging.getLogger(__name__)
+def _lock_ex(file_obj: IO[Any]) -> None:
+ if fcntl is not None:
+ fcntl.flock(file_obj.fileno(), fcntl.LOCK_EX)
+
+
+def _unlock(file_obj: IO[Any]) -> None:
+ if fcntl is not None:
+ fcntl.flock(file_obj.fileno(), fcntl.LOCK_UN)
+
+
class WebDiscussionSync:
def __init__(self, db: RoundtableDB):
self.db = db
@@ -89,14 +103,14 @@ def append_token_stream(self, web_dir: Path, event: dict[str, Any]) -> None:
target = web_dir / "token_stream.jsonl"
try:
with open(target, "a") as f:
- fcntl.flock(f.fileno(), fcntl.LOCK_EX)
+ _lock_ex(f)
try:
f.write(json.dumps(event, ensure_ascii=False, separators=(",", ":")))
f.write("\n")
f.flush()
os.fsync(f.fileno())
finally:
- fcntl.flock(f.fileno(), fcntl.LOCK_UN)
+ _unlock(f)
except Exception:
logger.debug("Failed to append event to token_stream.jsonl at %s", target)
@@ -108,7 +122,7 @@ def sync_state(self, web_dir: Path, discussion_id: str, conn: sqlite3.Connection
lock_path = json_path.with_suffix(".json.lock")
try:
with open(lock_path, "a") as lock_file:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
+ _lock_ex(lock_file)
try:
with open(json_path) as f:
data = json.load(f)
@@ -265,7 +279,7 @@ def sync_state(self, web_dir: Path, discussion_id: str, conn: sqlite3.Connection
os.rename(str(tmp), str(json_path))
logger.info("Synchronized web discussion.json for %s from database", discussion_id)
finally:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
+ _unlock(lock_file)
except Exception:
logger.exception("Failed to sync web discussion state for %s", discussion_id)
@@ -284,7 +298,7 @@ def update_speech(
lock_path = json_path.with_suffix(".json.lock")
try:
with open(lock_path, "a") as lock_file:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
+ _lock_ex(lock_file)
try:
with open(json_path) as f:
data = json.load(f)
@@ -317,7 +331,7 @@ def update_speech(
os.rename(str(tmp), str(json_path))
logger.info("Updated web discussion.json for %s (cross-process)", discussion_id)
finally:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
+ _unlock(lock_file)
except Exception:
logger.exception("Failed to update web discussion.json for %s", discussion_id)
@@ -329,7 +343,7 @@ def conclude(self, web_dir: Path, discussion_id: str, conclusion: str) -> None:
lock_path = json_path.with_suffix(".json.lock")
try:
with open(lock_path, "a") as lock_file:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX)
+ _lock_ex(lock_file)
try:
with open(json_path) as f:
data = json.load(f)
@@ -365,6 +379,6 @@ def conclude(self, web_dir: Path, discussion_id: str, conclusion: str) -> None:
os.rename(str(tmp), str(json_path))
logger.info("Concluded web discussion.json for %s (cross-process)", discussion_id)
finally:
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
+ _unlock(lock_file)
except Exception:
logger.exception("Failed to conclude web discussion.json for %s", discussion_id)
diff --git a/src/skills/.clawhubignore b/src/skills/.clawhubignore
index c9c1861..4fbda5a 100644
--- a/src/skills/.clawhubignore
+++ b/src/skills/.clawhubignore
@@ -25,3 +25,4 @@ node_modules/
.github/
docs/internal/
docs/product/
+references/
diff --git a/src/skills/SKILL.md b/src/skills/SKILL.md
index 35b5aa4..edcb1af 100644
--- a/src/skills/SKILL.md
+++ b/src/skills/SKILL.md
@@ -24,7 +24,7 @@ metadata:
## Publishing to skill hubs
-When preparing Roundtable for Hermes Skill Hub or OpenClaw/ClawHub, use the release checklist in `references/skill-hub-publishing.md`. Key reminders: keep `src/skills/` self-contained and free of private team data, include both Hermes and OpenClaw metadata blocks in `SKILL.md`, check `hermes skills publish --help`, `clawhub publish --help`, and `clawhub whoami`, and gate real publishing on user confirmation of target account/repo.
+Before publishing to Hermes Skill Hub or OpenClaw/ClawHub, run the repository release preflight and keep the skill package self-contained. Do not include repository-only notes, incident writeups, private chat transcripts, or secret-handling history in the public skill bundle. Confirm the target account/repository before running a real publish.
## Overview
@@ -367,28 +367,17 @@ resp = requests.get('https://open.feishu.cn/open-apis/im/v1/messages',
## Web Viewer (default ON)
-`run_demo()` has `web: bool = True` by default (changed from `False`). This means demo discussions automatically start a web viewer at `http://localhost:8199`. The viewer uses PM2 to manage an Express subprocess, fcntl for file locking, and nanoid for token generation.
+`roundtable_init` / `create_discussion` default to `web=True`. The Web Viewer is best-effort: discussion creation should still succeed when Node.js is missing, npm dependencies cannot be installed, or the viewer port is unavailable.
-**Browser auto-open**: Starting a discussion with `web=True` should automatically open the browser via `subprocess.run(["open", web_url])` in the Hermes adapter's `_handle_init`. This is an **adapter-level side-effect**, not a core-level one — the core library returns the URL but leaves UX actions to the adapter. The generic adapter intentionally does NOT auto-open (headless environments). If you need to customize browser behavior (e.g., open in a specific browser), modify `_handle_init` in `adapters/hermes.py`.
+Returned fields:
-**⚠️ Pitfall: Browser opens at END instead of START (2026-05-23)** — Boss reported the browser doesn't open when the discussion begins; it only opens after the discussion concludes (or manually). The `_handle_init` code has the `subprocess.run(["open", web_url])` call, but it may not fire reliably in all execution paths. **Diagnosis checklist**: (1) Verify `_handle_init` actually reaches the `subprocess.run` line (add logging) (2) Check if the `web_url` variable is correctly populated from `publisher.start()` return (3) Confirm the `open` command runs in the correct subprocess context (may need `shell=False` with list args on macOS). **See also**: Bug task `t_xxxxxxxx` for the specific fix.
+- `web_status`: `ready`, `failed`, or `disabled`
+- `web_url`: viewer URL when ready, otherwise `None`
+- `web_error` and `web_help`: clear diagnostics for viewer startup failures
-**⚠️ Pitfall: WebViewer real-time updates broken on macOS (2026-05-23)** — Boss reported that the WebViewer doesn't show new speeches in real-time; the browser must be force-refreshed to see updates. **Root cause**: The Express server (`server.mjs`) uses `fs.watch(DISCUSSION_PATH, ...)` to detect `discussion.json` changes and broadcast via SSE. But Python's `WebPublisher` uses atomic write: `write .json.tmp → os.rename()`. On macOS, `fs.watch()` on a file loses tracking after `rename()` replaces the inode — the watcher stays on the old inode, never sees the new file's changes. **Fix**: Change `fs.watch` to watch the **directory** instead of the file, then filter by filename:
-```javascript
-// Wrong — breaks on atomic rename:
-watch(DISCUSSION_PATH, () => { ... });
+Startup order: reuse a healthy local viewer, start `node server.mjs`, optionally install local npm dependencies with `npm install --omit=dev`, then retry. PM2 is optional only. Roundtable never installs Node itself and never performs silent global npm installs.
-// Correct — watches directory, catches rename:
-const dir = require("path").dirname(DISCUSSION_PATH);
-const filename = require("path").basename(DISCUSSION_PATH);
-watch(dir, (eventType, changedFilename) => {
- if (changedFilename !== filename) return;
- // ... debounce + broadcast logic unchanged
-});
-```
-**Alternative**: Add server-side polling fallback (`setInterval` + mtime check) as defense-in-depth. **See also**: Bug task `t_xxxxxxxx` for the specific fix.
-
-*Note: Tool calls executed natively on the platform handle environment and browser integration automatically.*
+Browser opening is adapter-level UX. Core and generic APIs return the URL and status fields; headless environments should read those fields instead of assuming a browser window exists.
### Iframe Embed Mode
@@ -434,7 +423,7 @@ Each round is evaluated for convergence:
## Conclusion Document Format
-The format below works for general discussions. For **product/design/dev discussions aimed at producing a buildable specification**, use the decision-oriented format instead — see `references/web-viewer-discussion-example.md` for the full pattern (MVP scope, tech architecture, acceptance criteria, risk assessment, design deliverables, action items).
+The format below works for general discussions. For **product/design/dev discussions aimed at producing a buildable specification**, prefer a decision-oriented format with MVP scope, technical architecture, acceptance criteria, risk assessment, design deliverables, and action items.
```markdown
# Roundtable Conclusion: [Topic]
@@ -493,24 +482,3 @@ kanban_comment(task_id="t_xxx", body="Roundtable conclusion: {conclusion_path}")
9. **WebViewer integration** — Setting `web=True` on `roundtable_init` automatically starts the WebViewer. It is supported across processes.
10. **Notifications config** — Ensure you pass the `notifications` configuration parameter to `roundtable_init` if you want events to sync to messaging groups (e.g. Feishu). The adapter handles `send_fn` wiring automatically.
11. **Developer Reference** — Maintainer-only implementation documentation exists separately and is not part of the agent execution workflow.
-
-## Test Results
-
-See `references/test-results-2026-05-20.md` for the first functional test results, including bugs found and product acceptance report.
-
-## Open-Source Release
-
-See `references/open-source-readiness.md` for the pre-release checklist (LICENSE, cleanup, adapter gaps, test isolation).
-
-## Working Examples
-
-- `references/opc-experience-discussion-example.md` — 4-round, 4-participant discussion with timing data and workflow
-- `references/notifications-example.md` — roundtable with real-time push notifications to Feishu
-- `references/release-planning-discussion.md` — 3-round product/design/dev discussion for open-source release planning
-- `references/ai-relay-open-source-discussion.md` — 3-round discussion with standard tool workflow, notifications, and conclusion doc → 5/29 release plan
-- `references/web-viewer-discussion-example.md` — Decision-oriented conclusion doc pattern: MVP scope, tech architecture, acceptance criteria, risk assessment, design deliverables. Use this format when the discussion goal is to produce a buildable specification.
-- `references/post-discussion-kanban-dispatch.md` — After discussion concludes, create kanban tasks grouped by owner, subscribe notifications, and dispatch to team via Feishu groups.
-
-## Open-Source Release Checklist
-
-See `references/open-source-readiness-checklist.md` for the pre-release audit: missing LICENSE, Hermes-specific files to separate, build-backend fix, .gitignore, internal docs to remove, generic adapter gaps, and target package structure.
diff --git a/tests/test_core_web_integration.py b/tests/test_core_web_integration.py
index 128f8c7..17be1cf 100644
--- a/tests/test_core_web_integration.py
+++ b/tests/test_core_web_integration.py
@@ -8,6 +8,11 @@
from __future__ import annotations
+import os
+import subprocess
+import sys
+import textwrap
+from pathlib import Path
from unittest.mock import MagicMock, patch
import pytest
@@ -37,6 +42,40 @@ def _mock_publisher():
return mock
+def test_import_roundtable_without_fcntl():
+ """Top-level import should work on platforms without fcntl (for example Windows)."""
+ code = textwrap.dedent(
+ """
+ import builtins
+
+ real_import = builtins.__import__
+
+ def fake_import(name, globals=None, locals=None, fromlist=(), level=0):
+ if name == "fcntl":
+ raise ImportError("simulated missing fcntl")
+ return real_import(name, globals, locals, fromlist, level)
+
+ builtins.__import__ = fake_import
+
+ import roundtable
+ from roundtable.web_sync import WebDiscussionSync
+
+ assert roundtable.__version__ == "2.1.0"
+ assert WebDiscussionSync.__name__ == "WebDiscussionSync"
+ """
+ )
+ env = os.environ.copy()
+ env["PYTHONPATH"] = str(Path.cwd() / "src")
+ result = subprocess.run(
+ [sys.executable, "-c", code],
+ env=env,
+ text=True,
+ capture_output=True,
+ check=False,
+ )
+ assert result.returncode == 0, result.stderr
+
+
# ---------------------------------------------------------------------------
# create_discussion with web=True
# ---------------------------------------------------------------------------
@@ -56,6 +95,9 @@ def test_web_starts_publisher(self, core):
assert result["ok"] is True
assert result["web_url"] == "http://localhost:8199/rt_test123"
+ assert result["web_status"] == "ready"
+ assert result["web_error"] is None
+ assert result["web_help"] is None
disc_id = result["discussion_id"]
assert core._publishers[disc_id] is mock_pub
mock_pub.start.assert_called_once()
@@ -71,20 +113,25 @@ def test_web_false_no_publisher(self, core):
)
assert result["ok"] is True
assert result["web_url"] is None
+ assert result["web_status"] == "disabled"
+ assert result["web_error"] is None
+ assert result["web_help"] is None
assert len(core._publishers) == 0
def test_web_failure_is_reported(self, core):
- """If web=True is requested, a WebPublisher startup failure is visible."""
- with (
- patch("roundtable.web_publisher.WebPublisher", side_effect=RuntimeError("PM2 not found")),
- pytest.raises(RuntimeError, match="Failed to start web viewer"),
- ):
- core.create_discussion(
+ """If web=True startup fails, discussion creation still succeeds with diagnostics."""
+ with patch("roundtable.web_publisher.WebPublisher", side_effect=RuntimeError("Node not found")):
+ result = core.create_discussion(
"Test topic",
_make_participants(),
web=True,
)
+ assert result["ok"] is True
+ assert result["web_url"] is None
+ assert result["web_status"] == "failed"
+ assert "Node not found" in result["web_error"]
+ assert "could not start the web viewer" in result["web_help"]
assert len(core._publishers) == 0
def test_publisher_stored_by_discussion_id(self, core):
diff --git a/tests/test_web_publisher.py b/tests/test_web_publisher.py
index c893e12..d86c37c 100644
--- a/tests/test_web_publisher.py
+++ b/tests/test_web_publisher.py
@@ -378,58 +378,134 @@ def test_stop_when_not_started(self, tmp_path):
# ---------------------------------------------------------------------------
-# start() — shared PM2 integration
+# start() — shared server startup integration
# ---------------------------------------------------------------------------
class TestSharedServer:
- @patch("roundtable.web_publisher.subprocess.run")
- def test_ensure_shared_starts_pm2_when_not_running(self, mock_run, tmp_path):
- # First call: pm2 jlist returns empty (not running)
- # Second call: pm2 start succeeds
- mock_run.side_effect = [
- MagicMock(returncode=0, stdout="[]"), # jlist
- MagicMock(returncode=0), # start
- ]
-
+ def test_ensure_shared_reuses_ready_roundtable_server(self, tmp_path):
pub = WebPublisher(str(tmp_path), port=19011)
- with patch.object(pub, "_wait_for_port"):
+ with (
+ patch.object(pub, "_is_roundtable_server_ready", return_value=True) as ready,
+ patch.object(pub, "_start_direct_node_server") as start_node,
+ ):
pub._ensure_shared_server_running()
- # Should have called pm2 start
- assert mock_run.call_count == 2
- start_cmd = mock_run.call_args_list[1][0][0]
- assert "pm2" in start_cmd
- assert "start" in start_cmd
- assert "--data-dir" in start_cmd
-
- @patch("roundtable.web_publisher.subprocess.run")
- def test_ensure_shared_skips_when_already_running(self, mock_run, tmp_path):
- # pm2 jlist returns the shared process as online
- jlist_output = json.dumps([{"name": "roundtable-web", "pm2_env": {"status": "online"}}])
- mock_run.return_value = MagicMock(returncode=0, stdout=jlist_output)
+ ready.assert_called()
+ start_node.assert_not_called()
+ def test_ensure_shared_starts_direct_node_when_not_running(self, tmp_path):
pub = WebPublisher(str(tmp_path), port=19012)
- with patch.object(pub, "_wait_for_port"):
+ with (
+ patch.object(pub, "_select_port", return_value=19012),
+ patch.object(pub, "_is_roundtable_server_ready", return_value=False),
+ patch.object(pub, "_is_shared_running", return_value=False),
+ patch("roundtable.web_publisher.shutil.which", side_effect=lambda name: f"/usr/bin/{name}"),
+ patch.object(pub, "_ensure_supported_node"),
+ patch.object(pub, "_start_direct_node_server", return_value=True) as start_node,
+ patch.object(pub, "_install_web_dependencies") as install_deps,
+ patch.object(pub, "_start_pm2_server") as start_pm2,
+ ):
pub._ensure_shared_server_running()
- # Should only have called pm2 jlist, not pm2 start
- assert mock_run.call_count == 1
+ start_node.assert_called_once()
+ install_deps.assert_not_called()
+ start_pm2.assert_not_called()
- @patch("roundtable.web_publisher.subprocess.run")
- def test_start_pm2_failure_raises(self, mock_run, tmp_path):
- mock_run.side_effect = [
- MagicMock(returncode=0, stdout="[]"), # jlist: not running
- MagicMock(returncode=1, stderr="PM2 error"), # start: fails
- MagicMock(returncode=0, stdout="[]"), # jlist re-check: still not running
- ]
+ def test_ensure_shared_installs_deps_and_retries_direct_node(self, tmp_path):
+ pub = WebPublisher(str(tmp_path), port=19013)
+
+ with (
+ patch.object(pub, "_select_port", return_value=19013),
+ patch.object(pub, "_is_roundtable_server_ready", return_value=False),
+ patch.object(pub, "_is_shared_running", return_value=False),
+ patch("roundtable.web_publisher.shutil.which", side_effect=lambda name: f"/usr/bin/{name}"),
+ patch.object(pub, "_ensure_supported_node"),
+ patch.object(pub, "_start_direct_node_server", side_effect=[False, True]) as start_node,
+ patch.object(
+ pub,
+ "_install_web_dependencies",
+ return_value="`npm install --omit=dev` completed",
+ ) as install,
+ patch.object(pub, "_start_pm2_server") as start_pm2,
+ ):
+ pub._ensure_shared_server_running()
+
+ assert start_node.call_count == 2
+ install.assert_called_once()
+ start_pm2.assert_not_called()
+ def test_install_web_dependencies_skips_puppeteer_browser_download(self, tmp_path):
pub = WebPublisher(str(tmp_path), port=19013)
- with pytest.raises(RuntimeError, match="PM2 start failed"):
+ project_dir = tmp_path / "npm-project"
+ project_dir.mkdir()
+
+ with (
+ patch.object(pub, "_find_npm_project_dir", return_value=project_dir),
+ patch("roundtable.web_publisher.shutil.which", return_value="/usr/bin/npm"),
+ patch("roundtable.web_publisher.subprocess.run") as run,
+ ):
+ run.return_value = MagicMock(returncode=0, stdout="", stderr="")
+ result = pub._install_web_dependencies()
+
+ assert result == "`npm install --omit=dev` completed"
+ kwargs = run.call_args.kwargs
+ assert kwargs["cwd"] == project_dir
+ assert kwargs["env"]["PUPPETEER_SKIP_DOWNLOAD"] == "true"
+
+ def test_ensure_shared_missing_node_raises_clear_error(self, tmp_path):
+ pub = WebPublisher(str(tmp_path), port=19014)
+
+ with (
+ patch.object(pub, "_select_port", return_value=19014),
+ patch.object(pub, "_is_roundtable_server_ready", return_value=False),
+ patch.object(pub, "_is_shared_running", return_value=False),
+ patch("roundtable.web_publisher.shutil.which", return_value=None),
+ pytest.raises(RuntimeError, match=r"Node.js 18\+ is required"),
+ ):
+ pub._ensure_shared_server_running()
+
+ def test_ensure_shared_all_startup_paths_fail_raises_diagnostic(self, tmp_path):
+ pub = WebPublisher(str(tmp_path), port=19015)
+
+ with (
+ patch.object(pub, "_select_port", return_value=19015),
+ patch.object(pub, "_is_roundtable_server_ready", return_value=False),
+ patch.object(pub, "_is_shared_running", return_value=False),
+ patch("roundtable.web_publisher.shutil.which", side_effect=lambda name: f"/usr/bin/{name}"),
+ patch.object(pub, "_ensure_supported_node"),
+ patch.object(pub, "_start_direct_node_server", return_value=False),
+ patch.object(pub, "_install_web_dependencies", return_value="npm failed"),
+ patch.object(pub, "_start_pm2_server", return_value="pm2 failed"),
+ pytest.raises(RuntimeError, match="Could not start Roundtable web viewer"),
+ ):
+ pub._ensure_shared_server_running()
+
+ def test_ensure_shared_old_node_raises_clear_error(self, tmp_path):
+ pub = WebPublisher(str(tmp_path), port=19016)
+
+ with (
+ patch.object(pub, "_select_port", return_value=19016),
+ patch.object(pub, "_is_roundtable_server_ready", return_value=False),
+ patch.object(pub, "_is_shared_running", return_value=False),
+ patch("roundtable.web_publisher.shutil.which", return_value="/usr/bin/node"),
+ patch("roundtable.web_publisher.subprocess.run") as run_node,
+ pytest.raises(RuntimeError, match=r"Node.js 18\+ is required"),
+ ):
+ run_node.return_value = MagicMock(returncode=0, stdout="v14.18.2", stderr="")
pub._ensure_shared_server_running()
+ @patch("roundtable.web_publisher.subprocess.run")
+ def test_is_shared_running_detects_pm2_online(self, mock_run, tmp_path):
+ jlist_output = json.dumps([{"name": "roundtable-web", "pm2_env": {"status": "online"}}])
+ mock_run.return_value = MagicMock(returncode=0, stdout=jlist_output)
+
+ pub = WebPublisher(str(tmp_path), port=19016)
+ with patch("roundtable.web_publisher.shutil.which", return_value="/usr/bin/pm2"):
+ assert pub._is_shared_running() is True
+
# ---------------------------------------------------------------------------
# Properties
diff --git a/tests/test_web_viewer.py b/tests/test_web_viewer.py
index be82627..97486a3 100644
--- a/tests/test_web_viewer.py
+++ b/tests/test_web_viewer.py
@@ -57,6 +57,7 @@ def discussion_dir(tmp_path):
disc_subdir.mkdir()
disc = {
"token_hash": hashlib.sha256(b"test_token_abc123").hexdigest(),
+ "owner_secret_hash": hashlib.sha256(b"owner_secret_123").hexdigest(),
"topic": "Test Discussion Topic",
"status": "completed",
"schema_version": 2,
@@ -249,6 +250,17 @@ def test_data_endpoint_returns_discussion(self, server):
assert len(data["participants"]) == 2
# Token should NOT be in the response
assert "token" not in data
+ assert "token_hash" not in data
+ assert "owner_secret_hash" not in data
+
+ def test_healthz_identifies_roundtable_server(self, server):
+ base, _, discussion_dir = server
+ resp = requests.get(f"{base}/healthz", timeout=5)
+ assert resp.status_code == 200
+ data = resp.json()
+ assert data["ok"] is True
+ assert data["service"] == "roundtable-web"
+ assert data["data_dir"] == str(discussion_dir)
# ---------------------------------------------------------------------------
@@ -295,8 +307,9 @@ def test_correct_password_sets_cookie(self, pw_server):
assert resp.status_code == 200
data = resp.json()
assert data["ok"] is True
- # Should set rt_pw_ cookie
- assert "rt_pw_pw_token_456" in resp.cookies
+ # Should set rt_pw_ cookie without exposing the raw token.
+ token_hash = hashlib.sha256(b"pw_token_456").hexdigest()
+ assert f"rt_pw_{token_hash}" in resp.cookies
def test_password_cookie_grants_access(self, pw_server):
"""After authenticating, the cookie should allow direct access."""
@@ -399,6 +412,8 @@ class TestExportPDF:
def test_export_pdf_success(self, server):
base, _, _ = server
resp = requests.get(f"{base}/api/test_token_abc123/export/pdf", timeout=90)
+ if resp.status_code == 503:
+ pytest.skip(f"PDF export unavailable in this environment: {resp.json().get('detail')}")
assert resp.status_code == 200
ct = resp.headers.get("content-type", "")
assert "pdf" in ct
@@ -409,6 +424,8 @@ def test_export_pdf_success(self, server):
def test_export_pdf_filename(self, server):
base, _, _ = server
resp = requests.get(f"{base}/api/test_token_abc123/export/pdf", timeout=90)
+ if resp.status_code == 503:
+ pytest.skip(f"PDF export unavailable in this environment: {resp.json().get('detail')}")
cd = resp.headers.get("content-disposition", "")
assert "Test_Discussion_Topic" in cd or "discussion" in cd
@@ -423,6 +440,87 @@ def test_export_pdf_password_protected(self, pw_server):
assert resp.status_code == 401
+# ---------------------------------------------------------------------------
+# Owner-only revoke tests
+# ---------------------------------------------------------------------------
+
+
+class TestRevokeAuthorization:
+ def test_revoke_without_owner_secret_returns_403(self, server):
+ base, _, _ = server
+ resp = requests.post(f"{base}/api/test_token_abc123/revoke", json={}, timeout=5)
+ assert resp.status_code == 403
+ assert "Owner authorization" in resp.json()["error"]
+
+ def test_revoke_with_wrong_owner_secret_returns_403(self, server):
+ base, _, _ = server
+ resp = requests.post(
+ f"{base}/api/test_token_abc123/revoke",
+ json={"owner_secret": "wrong"},
+ timeout=5,
+ )
+ assert resp.status_code == 403
+
+ def test_revoke_with_owner_secret_succeeds(self, server):
+ base, _, discussion_dir = server
+ resp = requests.post(
+ f"{base}/api/test_token_abc123/revoke",
+ json={"owner_secret": "owner_secret_123"},
+ timeout=5,
+ )
+ assert resp.status_code == 200
+ assert resp.json()["revoked"] is True
+
+ disc_path = discussion_dir / "test_disc" / "discussion.json"
+ data = json.loads(disc_path.read_text())
+ token_hash = hashlib.sha256(b"test_token_abc123").hexdigest()
+ assert token_hash in data["revoked_token_hashes"]
+
+ def test_revoke_preserves_cross_process_disk_updates(self, server):
+ base, _, discussion_dir = server
+ disc_path = discussion_dir / "test_disc" / "discussion.json"
+ data = json.loads(disc_path.read_text())
+ data["dispatch_summary"] = {"accepted": 2, "pending": 0}
+ data["speeches"].append(
+ {
+ "seq": 4,
+ "round": 1,
+ "agent_id": "carol",
+ "display_name": "Carol",
+ "participant": "carol",
+ "content": "A cross-process update that Node must preserve.",
+ }
+ )
+ disc_path.write_text(json.dumps(data, indent=2))
+
+ resp = requests.post(
+ f"{base}/api/test_token_abc123/revoke",
+ json={"owner_secret": "owner_secret_123"},
+ timeout=5,
+ )
+ assert resp.status_code == 200
+
+ result = json.loads(disc_path.read_text())
+ token_hash = hashlib.sha256(b"test_token_abc123").hexdigest()
+ assert token_hash in result["revoked_token_hashes"]
+ assert result["dispatch_summary"] == {"accepted": 2, "pending": 0}
+ assert any(s["agent_id"] == "carol" for s in result["speeches"])
+
+ def test_owner_viewer_config_contains_owner_capability(self, server):
+ base, _, _ = server
+ resp = requests.get(f"{base}/r/test_token_abc123?owner=owner_secret_123", timeout=5)
+ assert resp.status_code == 200
+ assert '"canRevoke":true' in resp.text
+ assert '"ownerSecret":"owner_secret_123"' in resp.text
+
+ def test_public_viewer_config_hides_owner_secret(self, server):
+ base, _, _ = server
+ resp = requests.get(f"{base}/r/test_token_abc123", timeout=5)
+ assert resp.status_code == 200
+ assert '"canRevoke":false' in resp.text
+ assert "owner_secret_123" not in resp.text
+
+
# ---------------------------------------------------------------------------
# i18n tests
# ---------------------------------------------------------------------------