From 1d88e8d6686659e53e5cd762f8a185f6b473d5d8 Mon Sep 17 00:00:00 2001
From: Roberto
Date: Wed, 19 Aug 2026 14:42:17 -0300
Subject: [PATCH 1/4] feat: public Workers MCP and private Charlie code-mode
Split the public surface onto a Cloudflare Worker (JSON sources only)
and keep FastAPI Tailscale-only. Add an Access-protected Charlie stack
with fail-closed origin token, Worker upstream bounds, and Tailscale
bind validation.
Co-authored-by: Cursor
---
ROADMAP.md | 2 +-
deploy/assert_tailscale_bind.py | 42 +
deploy/docker-compose.charlie-mcp.yml | 58 +
deploy/docker-compose.gvisor.yml | 15 +-
docs/DEPLOY_CHARLIE_MCP.md | 164 +
docs/DEPLOY_GVISOR.md | 62 +-
docs/DEPLOY_PUBLIC.md | 6 +-
docs/DEPLOY_WORKERS_MCP.md | 71 +
docs/MCP_SURFACE.md | 6 +-
src/findata/api/app.py | 23 +-
src/findata/api/mcp_app.py | 7 +-
src/findata/api/origin_guard.py | 47 +
tests/test_assert_tailscale_bind.py | 47 +
tests/test_origin_guard.py | 53 +
workers/mcp/.gitignore | 3 +
workers/mcp/package-lock.json | 4619 +++++++++++++++++++++++++
workers/mcp/package.json | 18 +
workers/mcp/public/index.html | 57 +
workers/mcp/src/catalog/focus.json | 17 +
workers/mcp/src/catalog/ibge.json | 51 +
workers/mcp/src/catalog/ipea.json | 38 +
workers/mcp/src/catalog/sgs.json | 428 +++
workers/mcp/src/index.ts | 22 +
workers/mcp/src/lib/http.ts | 79 +
workers/mcp/src/server.ts | 156 +
workers/mcp/src/tools/bcb.ts | 160 +
workers/mcp/src/tools/ibge.ts | 81 +
workers/mcp/src/tools/ipea.ts | 77 +
workers/mcp/src/tools/openfinance.ts | 118 +
workers/mcp/src/tools/tesouro.ts | 85 +
workers/mcp/tsconfig.json | 15 +
workers/mcp/worker-configuration.d.ts | 3 +
workers/mcp/wrangler.toml | 22 +
33 files changed, 6593 insertions(+), 59 deletions(-)
create mode 100644 deploy/assert_tailscale_bind.py
create mode 100644 deploy/docker-compose.charlie-mcp.yml
create mode 100644 docs/DEPLOY_CHARLIE_MCP.md
create mode 100644 docs/DEPLOY_WORKERS_MCP.md
create mode 100644 src/findata/api/origin_guard.py
create mode 100644 tests/test_assert_tailscale_bind.py
create mode 100644 tests/test_origin_guard.py
create mode 100644 workers/mcp/.gitignore
create mode 100644 workers/mcp/package-lock.json
create mode 100644 workers/mcp/package.json
create mode 100644 workers/mcp/public/index.html
create mode 100644 workers/mcp/src/catalog/focus.json
create mode 100644 workers/mcp/src/catalog/ibge.json
create mode 100644 workers/mcp/src/catalog/ipea.json
create mode 100644 workers/mcp/src/catalog/sgs.json
create mode 100644 workers/mcp/src/index.ts
create mode 100644 workers/mcp/src/lib/http.ts
create mode 100644 workers/mcp/src/server.ts
create mode 100644 workers/mcp/src/tools/bcb.ts
create mode 100644 workers/mcp/src/tools/ibge.ts
create mode 100644 workers/mcp/src/tools/ipea.ts
create mode 100644 workers/mcp/src/tools/openfinance.ts
create mode 100644 workers/mcp/src/tools/tesouro.ts
create mode 100644 workers/mcp/tsconfig.json
create mode 100644 workers/mcp/worker-configuration.d.ts
create mode 100644 workers/mcp/wrangler.toml
diff --git a/ROADMAP.md b/ROADMAP.md
index 48511b8..6f1ee16 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -1,6 +1,6 @@
# Roadmap & Next Steps
-Status: **v0.3.1 — alpha.** CI live at [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Public VPS+gVisor path: [`docs/DEPLOY_GVISOR.md`](docs/DEPLOY_GVISOR.md).
+Status: **v0.3.1 — alpha.** CI live at [`.github/workflows/ci.yml`](.github/workflows/ci.yml). Public MCP: [`docs/DEPLOY_WORKERS_MCP.md`](docs/DEPLOY_WORKERS_MCP.md). Internal FastAPI/gVisor: [`docs/DEPLOY_GVISOR.md`](docs/DEPLOY_GVISOR.md).
## 🟢 Ready to use right now
diff --git a/deploy/assert_tailscale_bind.py b/deploy/assert_tailscale_bind.py
new file mode 100644
index 0000000..e1eb1cb
--- /dev/null
+++ b/deploy/assert_tailscale_bind.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python3
+"""Refuse FastAPI publish addresses that are not Tailscale CGNAT.
+
+Compose interpolates TAILSCALE_IP into a host bind. A typo like 0.0.0.0
+would publish unauthenticated REST on the public interface.
+"""
+
+from __future__ import annotations
+
+import ipaddress
+import os
+import sys
+
+TAILSCALE_CGNAT = ipaddress.ip_network("100.64.0.0/10")
+_IPV4 = 4
+
+
+def allowed_tailscale_bind(raw: str) -> bool:
+ try:
+ address = ipaddress.ip_address(raw.strip())
+ except ValueError:
+ return False
+ if address.version != _IPV4:
+ return False
+ if address.is_unspecified or address.is_loopback or address.is_multicast:
+ return False
+ return address in TAILSCALE_CGNAT
+
+
+def main() -> int:
+ raw = os.environ.get("TAILSCALE_IP", "100.90.45.18")
+ if allowed_tailscale_bind(raw):
+ return 0
+ sys.stderr.write(
+ f"TAILSCALE_IP={raw!r} is not a Tailscale CGNAT IPv4 (100.64.0.0/10). "
+ "Refusing to bind. Set TAILSCALE_IP to the VPS tailnet address, never 0.0.0.0.\n"
+ )
+ return 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())
diff --git a/deploy/docker-compose.charlie-mcp.yml b/deploy/docker-compose.charlie-mcp.yml
new file mode 100644
index 0000000..3e5bd06
--- /dev/null
+++ b/deploy/docker-compose.charlie-mcp.yml
@@ -0,0 +1,58 @@
+# Private Charlie MCP — gVisor + code mode.
+# NEVER expose this stack without Cloudflare Tunnel + Access (service token).
+# Public surface is workers/mcp (no code mode). Internal REST is docker-compose.gvisor.yml.
+#
+# Usage:
+# docker compose -f deploy/docker-compose.charlie-mcp.yml up -d --build
+# # then cloudflared points at http://127.0.0.1:8001
+services:
+ openfindata-charlie-mcp:
+ build:
+ context: ..
+ dockerfile: Dockerfile
+ image: openfindata:latest
+ container_name: openfindata-charlie-mcp
+ runtime: runsc
+ restart: unless-stopped
+ ports:
+ # Loopback only — Tunnel (and optional Tailscale SSH tunnel) reach this.
+ - "127.0.0.1:8001:8000"
+ dns:
+ - 1.1.1.1
+ - 8.8.8.8
+ volumes:
+ - ./resolv.gvisor.conf:/etc/resolv.conf:ro
+ read_only: true
+ tmpfs:
+ - /tmp:mode=1777,size=256m
+ cap_drop:
+ - ALL
+ security_opt:
+ - no-new-privileges:true
+ mem_limit: 1024m
+ cpus: 1.5
+ user: "65534:65534"
+ pids_limit: 512
+ healthcheck:
+ disable: true
+ environment:
+ FINDATA_RATE_LIMIT_ENABLED: "true"
+ # Higher than public Worker; Charlie is authenticated and low-concurrency.
+ FINDATA_RATE_LIMIT_DEFAULT: "120/minute;5000/day"
+ # Private Charlie only — never set this on the public Worker or Traefik path.
+ FINDATA_MCP_CODE_MODE: "1"
+ # Fail-closed: compose refuses to start if this is unset/empty.
+ # Inject the same value as header X-Openfindata-Origin-Token from Charlie.
+ FINDATA_MCP_ORIGIN_TOKEN: "${FINDATA_MCP_ORIGIN_TOKEN:?set FINDATA_MCP_ORIGIN_TOKEN for Charlie code-mode}"
+ logging:
+ driver: json-file
+ options:
+ max-size: "10m"
+ max-file: "5"
+ networks:
+ - openfindata_charlie_net
+
+networks:
+ openfindata_charlie_net:
+ driver: bridge
+ # Isolated from hermes / wealthuman / public openfindata_net.
diff --git a/deploy/docker-compose.gvisor.yml b/deploy/docker-compose.gvisor.yml
index af755c4..f5bba57 100644
--- a/deploy/docker-compose.gvisor.yml
+++ b/deploy/docker-compose.gvisor.yml
@@ -12,7 +12,10 @@ services:
runtime: runsc
restart: unless-stopped
ports:
+ # Loopback + Tailscale CGNAT only. Never 0.0.0.0.
+ # Validate first: python3 deploy/assert_tailscale_bind.py
- "127.0.0.1:8000:8000"
+ - "${TAILSCALE_IP:-100.90.45.18}:8000:8000"
# gVisor does not reliably use Docker's 127.0.0.11 stub DNS.
# Mount a resolv.conf that points straight at public resolvers.
dns:
@@ -31,9 +34,7 @@ services:
cpus: 1.0
user: "65534:65534"
pids_limit: 256
- # Traefik v3 drops routers for Docker-unhealthy containers. The image
- # HEALTHCHECK hits /health; disable it here so a rate-limit blip cannot
- # take the public router offline. /health remains exempt in the app.
+ # Image HEALTHCHECK hits /health and can 429 under rate limit; keep off.
healthcheck:
disable: true
environment:
@@ -48,14 +49,6 @@ services:
max-file: "5"
networks:
- openfindata_net
- labels:
- - traefik.enable=true
- # Traefik runs in host network mode on this VPS — reach the published loopback port.
- # Production: export OPENFINDATA_HOST=api.seudominio.com before up.
- - traefik.http.routers.openfindata.rule=Host(`${OPENFINDATA_HOST:-findata.localhost}`)
- - traefik.http.routers.openfindata.entrypoints=websecure
- - traefik.http.routers.openfindata.tls.certresolver=letsencrypt
- - traefik.http.services.openfindata.loadbalancer.server.url=http://127.0.0.1:8000
networks:
openfindata_net:
diff --git a/docs/DEPLOY_CHARLIE_MCP.md b/docs/DEPLOY_CHARLIE_MCP.md
new file mode 100644
index 0000000..3c8f3db
--- /dev/null
+++ b/docs/DEPLOY_CHARLIE_MCP.md
@@ -0,0 +1,164 @@
+# Deploy privado: MCP Charlie (code mode)
+
+MCP **privado** do Charlie (Wealthuman): FastAPI + gVisor +
+`FINDATA_MCP_CODE_MODE=1`, publicado só via Cloudflare Tunnel + Access
+(service token). Não é superfície pública.
+
+| Superfície | Onde | Code mode |
+|---|---|---|
+| Público | Worker `workers/mcp` → `https://openfindata.com.br/mcp` | Não |
+| Interno REST | gVisor Tailscale `100.90.45.18:8000` | Não |
+| Charlie | `https://charlie-mcp.openfindata.com.br/mcp` | Sim |
+
+## Arquitetura
+
+```
+Charlie (Wealthuman Worker / agent)
+ → HTTPS charlie-mcp.openfindata.com.br
+ → Cloudflare Access (service token headers)
+ → Tunnel openfindata-charlie-mcp
+ → origin checa X-Openfindata-Origin-Token
+ → 127.0.0.1:8001
+ → container openfindata-charlie-mcp (runsc, CODE_MODE=1)
+```
+
+O Worker público **não** alcança Tailscale e **não** deve proxyar este
+host. Charlie chama o hostname Access-protegido com service token.
+
+## Compose (VPS)
+
+Arquivo: `deploy/docker-compose.charlie-mcp.yml`
+
+- Porta: `127.0.0.1:8001:8000` (loopback only)
+- Runtime: `runsc` (gVisor)
+- Rede isolada: `openfindata_charlie_net`
+- `FINDATA_MCP_CODE_MODE=1`
+- `FINDATA_MCP_ORIGIN_TOKEN` obrigatório (compose e processo recusam subir sem ele)
+- Healthcheck Docker desabilitado (mesmo motivo do stack gVisor público antigo)
+
+```bash
+cd /opt/openfindata-launch
+# openssl rand -hex 32
+export FINDATA_MCP_ORIGIN_TOKEN='...' # persistir em deploy/.env (gitignored)
+docker compose -f deploy/docker-compose.charlie-mcp.yml up -d --build
+curl -sS http://127.0.0.1:8001/health
+# /mcp no loopback sem X-Openfindata-Origin-Token deve ser 401
+```
+
+## Tunnel + Access
+
+Já provisionado (conta Robertoecf / Access org Wealthuman):
+
+| Recurso | Valor |
+|---|---|
+| Hostname | `charlie-mcp.openfindata.com.br` |
+| Tunnel | `openfindata-charlie-mcp` |
+| Ingress | `http://127.0.0.1:8001` |
+| Access app | Charlie openfindata MCP |
+| Service token name | `wealthuman-charlie-openfindata-mcp` |
+| cloudflared unit | `cloudflared-charlie` |
+| Token file | `/etc/cloudflared/openfindata-charlie.env` (root-only) |
+
+Política Access: **somente** service token (sem e-mail / browser login).
+
+O origin **não** confia só no Access. Com `CODE_MODE=1` o processo exige
+`FINDATA_MCP_ORIGIN_TOKEN` no boot e o header `X-Openfindata-Origin-Token`
+em todo path que não seja `/health`. O Charlie envia esse header junto
+com o service token; Access não o stripa. Loopback sem o header → **401**.
+
+### Headers obrigatórios
+
+```http
+CF-Access-Client-Id: .access
+CF-Access-Client-Secret:
+X-Openfindata-Origin-Token:
+```
+
+Guardar os três segredos no **Doppler Wealthuman** (nunca no git):
+
+```text
+OPENFINDATA_CHARLIE_MCP_URL=https://charlie-mcp.openfindata.com.br/mcp
+OPENFINDATA_CHARLIE_CF_ACCESS_CLIENT_ID=...
+OPENFINDATA_CHARLIE_CF_ACCESS_CLIENT_SECRET=...
+OPENFINDATA_CHARLIE_ORIGIN_TOKEN=...
+```
+
+## Consumo MCP (Streamable HTTP)
+
+fastapi-mcp é sessionful: use o `mcp-session-id` do `initialize` nas
+chamadas seguintes.
+
+```bash
+# 1) initialize → gravar mcp-session-id do response header
+# 2) notifications/initialized
+# 3) tools/list ou tools/call com o mesmo session id
+```
+
+Tools esperadas: ~26, incluindo `findata_run_code`, registry, ANBIMA,
+CVM/B3 paths (Python), além das séries públicas.
+
+Smoke mínimo (substituir secrets do Doppler):
+
+```bash
+# Sem Access → 403
+curl -sS -o /dev/null -w "%{http_code}\n" \
+ https://charlie-mcp.openfindata.com.br/health
+
+# Com Access → 200
+curl -sS -o /dev/null -w "%{http_code}\n" \
+ -H "CF-Access-Client-Id: $OPENFINDATA_CHARLIE_CF_ACCESS_CLIENT_ID" \
+ -H "CF-Access-Client-Secret: $OPENFINDATA_CHARLIE_CF_ACCESS_CLIENT_SECRET" \
+ https://charlie-mcp.openfindata.com.br/health
+```
+
+`findata_run_code` com `print(1+1)` deve retornar `output: "2\n"`.
+
+Preflight obrigatório (não basta `/health`):
+
+```bash
+# Loopback: MCP sem origin token
+curl -sS -o /dev/null -w "%{http_code}\n" \
+ -H 'content-type: application/json' \
+ -X POST http://127.0.0.1:8001/mcp \
+ --data '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"preflight","version":"0"}}}'
+# 401
+
+# Loopback: REST autenticado no origin
+curl -sS -o /dev/null -w "%{http_code}\n" \
+ -H "X-Openfindata-Origin-Token: $FINDATA_MCP_ORIGIN_TOKEN" \
+ http://127.0.0.1:8001/stats
+# 200
+
+# Público: tools/list no Worker NÃO contém findata_run_code
+```
+
+## Operação
+
+```bash
+# Status
+ssh monvanti-vps 'docker ps --filter name=openfindata-charlie; systemctl is-active cloudflared-charlie'
+
+# Logs
+ssh monvanti-vps 'docker logs --tail 100 openfindata-charlie-mcp'
+ssh monvanti-vps 'journalctl -u cloudflared-charlie -n 50 --no-pager'
+
+# Restart compose
+ssh monvanti-vps 'cd /opt/openfindata-launch && docker compose -f deploy/docker-compose.charlie-mcp.yml up -d'
+```
+
+## Segurança
+
+- Nunca publicar `8001` em `0.0.0.0` ou Traefik público.
+- Nunca ligar `FINDATA_MCP_CODE_MODE=1` no Worker ou no compose gVisor
+ Tailscale-only usado para REST interno.
+- Sem `FINDATA_MCP_ORIGIN_TOKEN` o container Charlie **não sobe**.
+- Rotacionar o service token **e** o origin token se vazou em chat/logs;
+ atualizar Doppler (Access + `OPENFINDATA_CHARLIE_ORIGIN_TOKEN`) e o env do compose.
+- Rate limit Charlie: `120/minute;5000/day` (compose).
+
+## Wiring Wealthuman
+
+No Charlie: MCP client Streamable HTTP apontando para
+`OPENFINDATA_CHARLIE_MCP_URL` com Access **e** `X-Openfindata-Origin-Token`
+em toda request (incluindo `initialize` e `tools/call`). Manter sessão
+(`mcp-session-id`) por conversa/agent run.
diff --git a/docs/DEPLOY_GVISOR.md b/docs/DEPLOY_GVISOR.md
index 6e71190..b0c8fd3 100644
--- a/docs/DEPLOY_GVISOR.md
+++ b/docs/DEPLOY_GVISOR.md
@@ -1,7 +1,11 @@
-# Deploy público com gVisor (VPS)
+# FastAPI interno com gVisor (VPS + Tailscale)
-Guia prático para subir o **Dados Financeiros Abertos** em VPS com runtime
-**runsc (gVisor)**, Traefik em host mode e rede isolada.
+O processo Python **não é a superfície pública**. MCP público:
+[`DEPLOY_WORKERS_MCP.md`](DEPLOY_WORKERS_MCP.md).
+
+Este guia sobe o FastAPI em VPS com runtime **runsc (gVisor)**,
+publicado só em loopback e no IP Tailscale — sem Traefik, sem 80/443
+para este serviço.
> Esta VPS **não tem KVM aninhado**. O gVisor em modo **systrap** é a camada de
> sandbox do processo do container, **não** uma segunda VM.
@@ -9,9 +13,7 @@ Guia prático para subir o **Dados Financeiros Abertos** em VPS com runtime
## Pré-requisitos
- Docker Engine com runtime **runsc** instalado
-- Traefik já em host mode na monvanti-vps (entrypoints `websecure`, certresolver
- `letsencrypt`)
-- Domínio apontando para a VPS
+- IP Tailscale da VPS (compose usa `TAILSCALE_IP`, default `100.90.45.18`)
### Instalar gVisor / runsc (snippet)
@@ -57,56 +59,37 @@ export OPENFINDATA_HOST=seu.dominio
## Subir o serviço
```bash
+export TAILSCALE_IP=$(tailscale ip -4)
+python3 deploy/assert_tailscale_bind.py # recusa 0.0.0.0 / IPs fora de 100.64.0.0/10
cd /opt/openfindata
docker compose -f deploy/docker-compose.gvisor.yml up -d --build
```
-O compose publica só em `127.0.0.1:8000` e usa labels Traefik. **Não** anexe esta
-rede a stacks hermes/wealthuman. **Não** habilite code mode
-(`FINDATA_MCP_CODE_MODE` deve permanecer ausente).
-
-
-## Traefik host mode + health
-
-Traefik on this VPS uses `network_mode: host`. Point the service at the published
-loopback port:
-
-```yaml
-traefik.http.services.openfindata.loadbalancer.server.url=http://127.0.0.1:8000
-```
-
-Do **not** set `traefik.docker.network=...` for this layout.
+O compose publica em `127.0.0.1:8000` e no IP Tailscale. **Sem** labels
+Traefik. **Não** anexe esta rede a stacks hermes/wealthuman. **Não** habilite
+code mode (`FINDATA_MCP_CODE_MODE` fica `"0"` neste compose).
-Traefik v3 **drops routers for Docker-unhealthy containers**. If `/health` is rate
-limited, the container goes unhealthy and public HTTPS returns Traefik
-`404 page not found` even while `curl 127.0.0.1:8000/health` still works.
-`/health` is rate-limit exempt for that reason.
-## Smoke checks
+## Smoke checks (Tailscale / loopback)
```bash
curl -sS http://127.0.0.1:8000/health
curl -sS http://127.0.0.1:8000/stats
curl -sS 'http://127.0.0.1:8000/bcb/series/name/selic?n=3'
-# MCP HTTP transport:
-curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/mcp
+# from a Tailscale peer:
+curl -sS "http://${TAILSCALE_IP:-100.90.45.18}:8000/health"
```
-Pelo domínio (via Traefik):
-
-```bash
-curl -sS "https://${OPENFINDATA_HOST}/health"
-curl -sS "https://${OPENFINDATA_HOST}/stats"
-curl -sS "https://${OPENFINDATA_HOST}/bcb/series/name/selic?n=3"
-```
+Público (Worker, não esta VPS): ver [`DEPLOY_WORKERS_MCP.md`](DEPLOY_WORKERS_MCP.md).
## Checklist de segurança
- [ ] `runtime: runsc` ativo no container
-- [ ] publish apenas em loopback (`127.0.0.1:8000`)
+- [ ] publish em loopback + IP Tailscale (não 0.0.0.0, sem Traefik)
- [ ] sem mount de `docker.sock`
- [ ] sem `network_mode: host`
-- [ ] code mode desligado (sem `FINDATA_MCP_CODE_MODE`)
+- [ ] `python3 deploy/assert_tailscale_bind.py` passou
+- [ ] code mode desligado (`FINDATA_MCP_CODE_MODE=0` neste compose)
- [ ] rede isolada `openfindata_net` (não compartilhada com hermes/wealthuman)
- [ ] limite de memória (`mem_limit: 512m`) e CPU (`cpus: 1.0`)
- [ ] `read_only: true`, `cap_drop: [ALL]`, `no-new-privileges:true`
@@ -127,6 +110,5 @@ docker inspect --format '{{.HostConfig.Runtime}}' openfindata
curl -v http://127.0.0.1:8000/health
```
-Se o Traefik não rotear, confira `OPENFINDATA_HOST`, se o Traefik enxerga a rede
-do container e se o entrypoint `websecure` + `letsencrypt` já estão válidos na
-monvanti-vps.
+Se o FastAPI não responder na Tailscale, confira `TAILSCALE_IP`, `ufw` (não
+expor 8000 na internet) e se o container ainda tem labels Traefik (não deve).
diff --git a/docs/DEPLOY_PUBLIC.md b/docs/DEPLOY_PUBLIC.md
index a3cd284..f205ae2 100644
--- a/docs/DEPLOY_PUBLIC.md
+++ b/docs/DEPLOY_PUBLIC.md
@@ -1,6 +1,10 @@
# Deploy público do Dados Financeiros Abertos no seu PC (WSL + Cloudflare Tunnel)
-> **VPS + gVisor:** para deploy em VPS com runtime runsc, veja [`docs/DEPLOY_GVISOR.md`](DEPLOY_GVISOR.md).
+> **MCP público (Workers):** [`docs/DEPLOY_WORKERS_MCP.md`](DEPLOY_WORKERS_MCP.md).
+> **FastAPI interno (VPS + gVisor + Tailscale):** [`docs/DEPLOY_GVISOR.md`](DEPLOY_GVISOR.md).
+
+> **Meta (este doc):** expor o FastAPI **no seu PC** via Cloudflare Tunnel — não é o
+> caminho de produção. Produção pública é o Worker.
> **Meta:** expor o **Dados Financeiros Abertos** como **servidor MCP público** — acessível via
> HTTPS, com TLS, rate limit, e _sem_ abrir porta no roteador nem pagar nada.
diff --git a/docs/DEPLOY_WORKERS_MCP.md b/docs/DEPLOY_WORKERS_MCP.md
new file mode 100644
index 0000000..84d9543
--- /dev/null
+++ b/docs/DEPLOY_WORKERS_MCP.md
@@ -0,0 +1,71 @@
+# Deploy público: MCP no Cloudflare Workers
+
+A superfície **pública** do openfindata é um Worker (`workers/mcp`):
+landing + MCP Streamable HTTP em `/mcp`.
+
+O FastAPI Python **não** fica na internet. Ele roda na VPS (gVisor),
+publicado só em loopback + IP Tailscale. REST/docs/CLI continuam aí.
+
+## Por que não proxy para o FastAPI
+
+O Worker **não alcança a Tailscale**. Encaminhar `/mcp` para a VPS
+recolocaria o processo Python no caminho público (via token/túnel).
+As tools deste Worker chamam as APIs JSON oficiais (BCB, IBGE, IPEA,
+SICONFI, Open Finance Directory).
+
+Fora deste Worker (CVM ZIP, B3 COTAHIST, ANBIMA XLS, registry FTS5,
+code mode): `pip install openfindata` ou FastAPI interno.
+
+## Deploy
+
+```bash
+cd workers/mcp
+npm install
+npx wrangler deploy
+```
+
+Custom domain (depois do smoke em `*.workers.dev`):
+
+```toml
+# wrangler.toml
+routes = [
+ { pattern = "openfindata.com.br", custom_domain = true },
+ { pattern = "www.openfindata.com.br", custom_domain = true },
+]
+```
+
+O custom domain no Cloudflare precisa DNS proxied (laranja). O registro
+A grey-cloud para o IP da VPS deve sair.
+
+Smoke:
+
+```bash
+curl -sS https://openfindata.com.br/health
+# POST JSON-RPC tools/list against /mcp with an MCP client
+# tools/list NÃO deve incluir findata_run_code
+```
+
+Upstream calls no Worker têm timeout (15s) e teto de payload (2 MB;
+8 MB só no Directory Open Finance). Séries BCB sem intervalo caem em
+`last_n≤200`. Coloque um rate limit no hostname no dashboard Cloudflare
+(WAF / Rate limiting rules) — o Worker não tem SlowAPI.
+
+## FastAPI interno (Tailscale)
+
+No compose gVisor: sem labels Traefik; portas `127.0.0.1:8000` e
+`${TAILSCALE_IP}:8000`.
+
+```bash
+curl http://100.90.45.18:8000/health # na Tailscale
+curl http://127.0.0.1:8000/docs # na própria VPS
+```
+
+Túnel Cloudflare (opcional, HTTPS interno): `cloudflared` → `127.0.0.1:8000`
+com Access allowlist (e-mail / WARP). Isso **não** é o MCP público.
+
+## Code mode
+
+`findata_run_code` **não** entra no Worker público.
+
+Para Charlie (Wealthuman): MCP privado com code mode via Tunnel + Access —
+ver [DEPLOY_CHARLIE_MCP.md](./DEPLOY_CHARLIE_MCP.md).
diff --git a/docs/MCP_SURFACE.md b/docs/MCP_SURFACE.md
index 0499b57..79c15cf 100644
--- a/docs/MCP_SURFACE.md
+++ b/docs/MCP_SURFACE.md
@@ -1,7 +1,9 @@
# MCP surface: curated tools over the REST API
-> Status: implemented (alpha curated catalog). Non-breaking: the REST API
-> is untouched. Implemented in [`src/findata/api/mcp_app.py`](../src/findata/api/mcp_app.py).
+> Status: implemented (alpha curated catalog). REST is untouched.
+> Internal MCP (FastAPI): [`src/findata/api/mcp_app.py`](../src/findata/api/mcp_app.py).
+> Public MCP (Cloudflare Worker, JSON sources only): [`workers/mcp`](../workers/mcp)
+> and [`docs/DEPLOY_WORKERS_MCP.md`](DEPLOY_WORKERS_MCP.md).
## Problem
diff --git a/src/findata/api/app.py b/src/findata/api/app.py
index 84c71ee..3932261 100644
--- a/src/findata/api/app.py
+++ b/src/findata/api/app.py
@@ -3,17 +3,23 @@
from __future__ import annotations
import time
-from collections.abc import AsyncIterator
+from collections.abc import AsyncIterator, Awaitable, Callable
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
-from fastapi.responses import HTMLResponse, JSONResponse
+from fastapi.responses import HTMLResponse, JSONResponse, Response
from fastapi.staticfiles import StaticFiles
from slowapi.middleware import SlowAPIMiddleware
from findata import __version__ as _pkg_version
from findata._limits import RateLimitExceeded, _rate_limit_exceeded_handler, limiter
+from findata.api.origin_guard import (
+ ORIGIN_TOKEN_HEADER,
+ assert_code_mode_origin_configured,
+ is_health_path,
+ origin_token_authorized,
+)
from findata.api.routers import (
anbima,
aneel,
@@ -78,6 +84,7 @@ def _resolve_version() -> str:
@asynccontextmanager
async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
+ assert_code_mode_origin_configured()
yield
await close_client()
# Shut the optional B3 thread pool down only if it was ever created.
@@ -120,6 +127,18 @@ async def lifespan(_app: FastAPI) -> AsyncIterator[None]:
app.add_middleware(SlowAPIMiddleware)
+@app.middleware("http")
+async def _origin_token_if_code_mode(
+ request: Request,
+ call_next: Callable[[Request], Awaitable[Response]],
+) -> Response:
+ if is_health_path(request.url.path) or origin_token_authorized(
+ request.headers.get(ORIGIN_TOKEN_HEADER)
+ ):
+ return await call_next(request)
+ return JSONResponse(status_code=401, content={"detail": "origin token required"})
+
+
@app.exception_handler(ValueError)
async def _value_error_handler(_: Request, exc: ValueError) -> JSONResponse:
return JSONResponse(status_code=400, content={"detail": str(exc)})
diff --git a/src/findata/api/mcp_app.py b/src/findata/api/mcp_app.py
index b3d6813..d693ffb 100644
--- a/src/findata/api/mcp_app.py
+++ b/src/findata/api/mcp_app.py
@@ -19,7 +19,9 @@
A, curation: only the headline tools are exposed, with real descriptions.
B, consolidation: ``bcb_*``/``cvm_*``/``tesouro_*``… fold many routes into one.
C, code mode: optional ``findata_run_code`` runs a Python snippet against the
- library (gated by ``FINDATA_MCP_CODE_MODE=1``; off by default).
+ library (gated by ``FINDATA_MCP_CODE_MODE=1``; off by default). When on,
+ ``FINDATA_MCP_ORIGIN_TOKEN`` is required at startup and on every non-health
+ request.
"""
from __future__ import annotations
@@ -859,6 +861,7 @@ class RunCodeRequest(BaseModel):
code: str = Field(
...,
+ max_length=50_000,
description="Python source to execute. The `findata` library is importable. "
"Source functions are async, wrap calls in asyncio.run(). Print results to stdout.",
)
@@ -929,7 +932,7 @@ async def findata_run_code(payload: RunCodeRequest) -> Any:
Security: runs in an isolated child interpreter with a timeout and output
cap, but is NOT a hardened sandbox. Enabled only when the server sets
- FINDATA_MCP_CODE_MODE=1.
+ FINDATA_MCP_CODE_MODE=1 and FINDATA_MCP_ORIGIN_TOKEN (fail-closed).
"""
return await _execute_code(payload.code, payload.timeout_s)
diff --git a/src/findata/api/origin_guard.py b/src/findata/api/origin_guard.py
new file mode 100644
index 0000000..ed7ed19
--- /dev/null
+++ b/src/findata/api/origin_guard.py
@@ -0,0 +1,47 @@
+"""Fail-closed origin token when MCP code mode is on.
+
+Cloudflare Access sits at the edge. This check is the origin's own gate so a
+misconfigured tunnel, a local port-forward, or a skipped Access policy cannot
+reach ``findata_run_code``. Health probes stay exempt.
+"""
+
+from __future__ import annotations
+
+import hmac
+import os
+
+ORIGIN_TOKEN_ENV = "FINDATA_MCP_ORIGIN_TOKEN" # noqa: S105
+ORIGIN_TOKEN_HEADER = "x-openfindata-origin-token" # noqa: S105
+_CODE_MODE_TRUTHY = frozenset({"1", "true", "yes", "on"})
+_HEALTH_PATH = "/health"
+
+
+def code_mode_enabled() -> bool:
+ return os.getenv("FINDATA_MCP_CODE_MODE", "").strip().lower() in _CODE_MODE_TRUTHY
+
+
+def configured_origin_token() -> str:
+ return os.getenv(ORIGIN_TOKEN_ENV, "").strip()
+
+
+def assert_code_mode_origin_configured() -> None:
+ if code_mode_enabled() and not configured_origin_token():
+ raise RuntimeError(
+ "FINDATA_MCP_CODE_MODE is on but FINDATA_MCP_ORIGIN_TOKEN is empty. "
+ "Refusing to start. Set a random origin token and inject it from "
+ "the tunnel as X-Openfindata-Origin-Token."
+ )
+
+
+def is_health_path(path: str) -> bool:
+ return path.rstrip("/") == _HEALTH_PATH
+
+
+def origin_token_authorized(header_value: str | None) -> bool:
+ if not code_mode_enabled():
+ return True
+ expected = configured_origin_token()
+ provided = (header_value or "").strip()
+ if not expected or not provided or len(provided) != len(expected):
+ return False
+ return hmac.compare_digest(provided, expected)
diff --git a/tests/test_assert_tailscale_bind.py b/tests/test_assert_tailscale_bind.py
new file mode 100644
index 0000000..8c517fb
--- /dev/null
+++ b/tests/test_assert_tailscale_bind.py
@@ -0,0 +1,47 @@
+"""TAILSCALE_IP must be a Tailscale CGNAT address, not a wildcard bind."""
+
+from __future__ import annotations
+
+import importlib.util
+import os
+import subprocess
+import sys
+from pathlib import Path
+
+import pytest
+
+REPO = Path(__file__).resolve().parents[1]
+SCRIPT = REPO / "deploy" / "assert_tailscale_bind.py"
+
+
+def _bind_mod():
+ spec = importlib.util.spec_from_file_location("assert_tailscale_bind", SCRIPT)
+ assert spec is not None and spec.loader is not None
+ module = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(module)
+ return module
+
+
+def test_allows_tailscale_cgnat() -> None:
+ allowed = _bind_mod().allowed_tailscale_bind
+ assert allowed("100.90.45.18") is True
+ assert allowed("100.64.0.1") is True
+
+
+@pytest.mark.parametrize("raw", ["0.0.0.0", "127.0.0.1", "8.8.8.8", "192.168.0.1", "not-an-ip", ""])
+def test_rejects_non_tailscale_binds(raw: str) -> None:
+ assert _bind_mod().allowed_tailscale_bind(raw) is False
+
+
+def test_script_exits_nonzero_for_wildcard() -> None:
+ env = os.environ.copy()
+ env["TAILSCALE_IP"] = "0.0.0.0"
+ result = subprocess.run(
+ [sys.executable, str(SCRIPT)],
+ env=env,
+ check=False,
+ capture_output=True,
+ text=True,
+ )
+ assert result.returncode == 1
+ assert "0.0.0.0" in result.stderr
diff --git a/tests/test_origin_guard.py b/tests/test_origin_guard.py
new file mode 100644
index 0000000..ab8ac89
--- /dev/null
+++ b/tests/test_origin_guard.py
@@ -0,0 +1,53 @@
+"""Origin token is required when MCP code mode is on."""
+
+from __future__ import annotations
+
+import pytest
+from fastapi.testclient import TestClient
+
+from findata.api.app import app
+from findata.api.origin_guard import (
+ ORIGIN_TOKEN_HEADER,
+ assert_code_mode_origin_configured,
+ origin_token_authorized,
+)
+
+
+def test_code_mode_off_allows_requests_without_origin_token(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.delenv("FINDATA_MCP_CODE_MODE", raising=False)
+ monkeypatch.delenv("FINDATA_MCP_ORIGIN_TOKEN", raising=False)
+ assert_code_mode_origin_configured()
+ assert origin_token_authorized(None) is True
+
+
+def test_code_mode_refuses_to_start_without_origin_token(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.setenv("FINDATA_MCP_CODE_MODE", "1")
+ monkeypatch.delenv("FINDATA_MCP_ORIGIN_TOKEN", raising=False)
+ with pytest.raises(RuntimeError, match="FINDATA_MCP_ORIGIN_TOKEN"):
+ assert_code_mode_origin_configured()
+
+
+def test_code_mode_rejects_missing_or_wrong_header(monkeypatch: pytest.MonkeyPatch) -> None:
+ monkeypatch.setenv("FINDATA_MCP_CODE_MODE", "1")
+ monkeypatch.setenv("FINDATA_MCP_ORIGIN_TOKEN", "origin-secret")
+ assert_code_mode_origin_configured()
+ assert origin_token_authorized(None) is False
+ assert origin_token_authorized("wrong") is False
+ assert origin_token_authorized("origin-secret") is True
+
+
+def test_code_mode_http_requires_origin_token_except_health(
+ monkeypatch: pytest.MonkeyPatch,
+) -> None:
+ monkeypatch.setenv("FINDATA_MCP_CODE_MODE", "1")
+ monkeypatch.setenv("FINDATA_MCP_ORIGIN_TOKEN", "origin-secret")
+ client = TestClient(app)
+ assert client.get("/health").status_code == 200
+ denied = client.get("/stats")
+ assert denied.status_code == 401
+ allowed = client.get("/stats", headers={ORIGIN_TOKEN_HEADER: "origin-secret"})
+ assert allowed.status_code == 200
diff --git a/workers/mcp/.gitignore b/workers/mcp/.gitignore
new file mode 100644
index 0000000..a933f10
--- /dev/null
+++ b/workers/mcp/.gitignore
@@ -0,0 +1,3 @@
+node_modules/
+.wrangler/
+.dev.vars
diff --git a/workers/mcp/package-lock.json b/workers/mcp/package-lock.json
new file mode 100644
index 0000000..38e995c
--- /dev/null
+++ b/workers/mcp/package-lock.json
@@ -0,0 +1,4619 @@
+{
+ "name": "openfindata-mcp",
+ "version": "0.3.1",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "openfindata-mcp",
+ "version": "0.3.1",
+ "dependencies": {
+ "@cloudflare/workers-types": "^5.20260813.1",
+ "@modelcontextprotocol/server": "^2.0.0",
+ "agents": "^0.20.1",
+ "wrangler": "^4.122.0",
+ "zod": "^4.4.3"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-8.0.0.tgz",
+ "integrity": "sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^8.0.0",
+ "js-tokens": "^10.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-8.0.0.tgz",
+ "integrity": "sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-8.0.1.tgz",
+ "integrity": "sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^8.0.0",
+ "@babel/generator": "^8.0.0",
+ "@babel/helper-compilation-targets": "^8.0.0",
+ "@babel/helpers": "^8.0.0",
+ "@babel/parser": "^8.0.0",
+ "@babel/template": "^8.0.0",
+ "@babel/traverse": "^8.0.0",
+ "@babel/types": "^8.0.0",
+ "@types/gensync": "^1.0.5",
+ "convert-source-map": "^2.0.0",
+ "empathic": "^2.0.1",
+ "gensync": "^1.0.0-beta.2",
+ "import-meta-resolve": "^4.2.0",
+ "json5": "^2.2.3",
+ "obug": "^2.1.1",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-8.0.0.tgz",
+ "integrity": "sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^8.0.0",
+ "@babel/types": "^8.0.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "@types/jsesc": "^2.5.0",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-annotate-as-pure": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz",
+ "integrity": "sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz",
+ "integrity": "sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/compat-data": "^8.0.0",
+ "@babel/helper-validator-option": "^8.0.0",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^11.0.0",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-create-class-features-plugin": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.1.tgz",
+ "integrity": "sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-annotate-as-pure": "^8.0.0",
+ "@babel/helper-member-expression-to-functions": "^8.0.0",
+ "@babel/helper-optimise-call-expression": "^8.0.0",
+ "@babel/helper-replace-supers": "^8.0.1",
+ "@babel/helper-skip-transparent-expression-wrappers": "^8.0.0",
+ "@babel/traverse": "^8.0.0",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-8.0.0.tgz",
+ "integrity": "sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==",
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-member-expression-to-functions": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.0.tgz",
+ "integrity": "sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^8.0.0",
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-optimise-call-expression": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz",
+ "integrity": "sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz",
+ "integrity": "sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==",
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-replace-supers": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz",
+ "integrity": "sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-member-expression-to-functions": "^8.0.0",
+ "@babel/helper-optimise-call-expression": "^8.0.0",
+ "@babel/traverse": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/helper-skip-transparent-expression-wrappers": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz",
+ "integrity": "sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^8.0.0",
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz",
+ "integrity": "sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.4.tgz",
+ "integrity": "sha512-4wFaiLd0bVo4cIoTXI3zKI038NIWE/cr3jvBjejOVYVxV/m8Ltav1USiGzG1fmS5J2RhgEOgXNNK46cRPnRsrg==",
+ "license": "MIT",
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz",
+ "integrity": "sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-8.0.0.tgz",
+ "integrity": "sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/template": "^8.0.0",
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-8.0.4.tgz",
+ "integrity": "sha512-srpptsAkEbbNIC/q8nT7o+m6CQe8CJUTV/t7MYc9NnWlgYVtHOb7JH6SorxMhN0kuRJjVqXbKClG6xSbPtzz+g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^8.0.4"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/plugin-proposal-decorators": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-8.0.2.tgz",
+ "integrity": "sha512-+C6O6KKXU7BBq1GNaIkFJxrALUVGRcr+WeWm4OcuRl3h+l/CmNfcTLMrT2Lm3uvGBimBH/8pEBRrXJFLoO67Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-create-class-features-plugin": "^8.0.1",
+ "@babel/helper-plugin-utils": "^8.0.1",
+ "@babel/plugin-syntax-decorators": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-syntax-decorators": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-8.0.1.tgz",
+ "integrity": "sha512-NI+0S/6MvR6GlcQFwjDZ+WIc2qvG6TXN534lYs9llNldwW4b7Dh6KTtk030FA0xWdYGs4t1lWo+OEWN8wGB+Nw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^8.0.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^8.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/runtime-corejs3": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.29.7.tgz",
+ "integrity": "sha512-ppj9ouYku+RX0ljtgZd+KMO5mkM2bCqg8H2PYAFWnLsHEIKIdRojqbJ2i3eVHrisuxy7nOFCmngTDdWtUCdXUQ==",
+ "license": "MIT",
+ "dependencies": {
+ "core-js-pure": "^3.48.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-8.0.0.tgz",
+ "integrity": "sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^8.0.0",
+ "@babel/parser": "^8.0.0",
+ "@babel/types": "^8.0.0"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-8.0.4.tgz",
+ "integrity": "sha512-bZnmqzGG8UZneG1lLxBoWIH0G6Gr1D846Yu4/3XnY6FhCndMR49u26nTY08u/dAxWmLWF9vGQOuC+84FfIUoeg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^8.0.0",
+ "@babel/generator": "^8.0.0",
+ "@babel/helper-globals": "^8.0.0",
+ "@babel/parser": "^8.0.4",
+ "@babel/template": "^8.0.0",
+ "@babel/types": "^8.0.4",
+ "obug": "^2.1.1"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "8.0.4",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-8.0.4.tgz",
+ "integrity": "sha512-eY+Yn3dCqTGmyiq2QRU66lA5FL8lqqqvecHt0fF3uHONIa7ToYsaCiWV8lOKqAs0Rb2SjixiKFROngnulPtt2g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^8.0.0",
+ "@babel/helper-validator-identifier": "^8.0.4"
+ },
+ "engines": {
+ "node": "^22.18.0 || >=24.11.0"
+ }
+ },
+ "node_modules/@cfworker/json-schema": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/@cfworker/json-schema/-/json-schema-4.1.1.tgz",
+ "integrity": "sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==",
+ "license": "MIT"
+ },
+ "node_modules/@cloudflare/kv-asset-handler": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.5.0.tgz",
+ "integrity": "sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==",
+ "license": "MIT OR Apache-2.0",
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "node_modules/@cloudflare/unenv-preset": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.16.1.tgz",
+ "integrity": "sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==",
+ "license": "MIT OR Apache-2.0",
+ "peerDependencies": {
+ "unenv": "2.0.0-rc.24",
+ "workerd": ">1.20260305.0 <2.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "workerd": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-64": {
+ "version": "1.20260811.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20260811.1.tgz",
+ "integrity": "sha512-i5jqz+ywtOefr0AJbiAc8qxBLfSim/B0WJG7aW3B+pWnoVfMJdUQvi+BWcFKZJ0MoCci3KadTx6g31VfuEEqpQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-darwin-arm64": {
+ "version": "1.20260811.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20260811.1.tgz",
+ "integrity": "sha512-NoOUM/nvaDdm2Onlnz33FikWjtatzulNtvwvy4xs0IrHaTCHwC0c8NwIt6s+AI13FkDs02/vm2I3GTPLCT9+hQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-64": {
+ "version": "1.20260811.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20260811.1.tgz",
+ "integrity": "sha512-sdYq2jL1AD1supa3fsi5O4zTB28wSjvTHj7Migh6/ts8EROPdvrSwv+rdGHhv8HJNAz/wbIAY3wZsi1Rw4uUIg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-linux-arm64": {
+ "version": "1.20260811.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20260811.1.tgz",
+ "integrity": "sha512-RIRv4shbu1kg05sD+DHTpSFCNnb5Dl2SkPDMUykqZa508tkPqe7VVw7gO0Q5msTBGyL0FfFrLuRxwwfA8u5Sow==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workerd-windows-64": {
+ "version": "1.20260811.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20260811.1.tgz",
+ "integrity": "sha512-g6VquwjASlYAibcNW/0E6Zszht4qLkmnXOGwIjjRHl2A0Qz48kVeMcGvyH6eA0G9U3OzZojjYFpP+YeyQmmdjw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@cloudflare/workers-types": {
+ "version": "5.20260813.1",
+ "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-5.20260813.1.tgz",
+ "integrity": "sha512-RQNfm7xD10hNHEQZFxQPmyGMJ9+aDGPcdFZ0x1LtmjRoLFcgZkGvfaqJAbOMQBAUFSESO3bYJS4p9mOLv28Ihg==",
+ "license": "MIT OR Apache-2.0"
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support/node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.11.3",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz",
+ "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
+ "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz",
+ "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz",
+ "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz",
+ "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz",
+ "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz",
+ "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz",
+ "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz",
+ "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz",
+ "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz",
+ "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz",
+ "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz",
+ "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz",
+ "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz",
+ "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz",
+ "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz",
+ "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz",
+ "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz",
+ "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz",
+ "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz",
+ "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz",
+ "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz",
+ "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz",
+ "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.0.tgz",
+ "integrity": "sha512-XovyyCCnBzW+zKu+z/zq8hwNs4KOR5rEMAOxo2f40Q5xoOI37IMm6MIg2COOUtUApo0i6850MTBKH2u4QLGIqg==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=20"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz",
+ "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz",
+ "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-freebsd-wasm32": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz",
+ "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==",
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.2"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz",
+ "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz",
+ "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz",
+ "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz",
+ "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz",
+ "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz",
+ "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz",
+ "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz",
+ "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz",
+ "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz",
+ "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz",
+ "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==",
+ "cpu": [
+ "arm"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz",
+ "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz",
+ "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz",
+ "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz",
+ "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz",
+ "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz",
+ "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz",
+ "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.1"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz",
+ "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==",
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.11.1"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-webcontainers-wasm32": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz",
+ "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/sharp-wasm32": "0.35.2"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz",
+ "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz",
+ "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz",
+ "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@modelcontextprotocol/client": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/client/-/client-2.0.0.tgz",
+ "integrity": "sha512-8f1OghQ2rjzIOfqgUCP+8GiUWqRs89njoWLNqAe8kWmDePv3s1fZXseej+QXemssEuuOvLLmLO/kqM3IQHtISw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@modelcontextprotocol/core": "2.0.0",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "jose": "^6.1.3",
+ "pkce-challenge": "^5.0.0",
+ "zod": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@modelcontextprotocol/core": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/core/-/core-2.0.0.tgz",
+ "integrity": "sha512-pJCEwGG7Lfr/+PQp9ZTwKXNeO5wzbfKL7H3MYpCorM4oFBoQrdjnBgEoqG+RjhsvS1FKrDbKux+M1HhlnGWqcA==",
+ "license": "MIT",
+ "dependencies": {
+ "zod": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@modelcontextprotocol/sdk": {
+ "version": "1.30.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.30.0.tgz",
+ "integrity": "sha512-xKd8OIzlqNzcqcNumGAa6g+PW2kjD5vrpcKOnfldAUPP3j7lnqMPwlTXQm8gF+UwH72z0lqaRbjr9hqGz0eITA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@hono/node-server": "^1.19.9 || ^2.0.5",
+ "ajv": "^8.17.1",
+ "ajv-formats": "^3.0.1",
+ "content-type": "^1.0.5",
+ "cors": "^2.8.5",
+ "cross-spawn": "^7.0.5",
+ "eventsource": "^3.0.2",
+ "eventsource-parser": "^3.0.0",
+ "express": "^5.2.1",
+ "express-rate-limit": "^8.2.1",
+ "hono": "^4.11.4",
+ "jose": "^6.1.3",
+ "json-schema-typed": "^8.0.2",
+ "pkce-challenge": "^5.0.0",
+ "raw-body": "^3.0.0",
+ "zod": "^3.25 || ^4.0",
+ "zod-to-json-schema": "^3.25.1"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@cfworker/json-schema": "^4.1.1",
+ "zod": "^3.25 || ^4.0"
+ },
+ "peerDependenciesMeta": {
+ "@cfworker/json-schema": {
+ "optional": true
+ },
+ "zod": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/@modelcontextprotocol/server": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@modelcontextprotocol/server/-/server-2.0.0.tgz",
+ "integrity": "sha512-YhHWdHfpFMQfd0prsEnxKeS3Qz3ytIGmsS0sth4KDjnacIT7hxk6hXHkJ9KysxlkvTM+WZAtQbbcUhdoP4Hvtw==",
+ "license": "MIT",
+ "dependencies": {
+ "@modelcontextprotocol/core": "2.0.0",
+ "zod": "^4.2.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.144.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.144.0.tgz",
+ "integrity": "sha512-nuhZIOLuI6TFQ32I/WnUx+SCPY7SdSKwgnFHydAuoS1+Z4BRcaP+RRJmGzl9lw+0OFF7UmaESf7KQRXaNLHypg==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@poppinss/colors": {
+ "version": "4.1.6",
+ "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.6.tgz",
+ "integrity": "sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==",
+ "license": "MIT",
+ "dependencies": {
+ "kleur": "^4.1.5"
+ }
+ },
+ "node_modules/@poppinss/dumper": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz",
+ "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==",
+ "license": "MIT",
+ "dependencies": {
+ "@poppinss/colors": "^4.1.5",
+ "@sindresorhus/is": "^7.0.2",
+ "supports-color": "^10.0.0"
+ }
+ },
+ "node_modules/@poppinss/exception": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.3.tgz",
+ "integrity": "sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==",
+ "license": "MIT"
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.4.tgz",
+ "integrity": "sha512-jHC2cnyKz5xU2fhECtFl8OZ83cYNt13GZQD+0uMJ/X3o+ijmd56okHhTUwxVSHPx1IRVIJEZ1/1pPzeLCU6XKA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-Dc5mPD8F5F/FS8i01syd7FTF6yB2fVthH/TRkjwJkzUK6EpoxHtqvZQP5Zwq80/5z19TWYHIg1KOHboCgVx/aQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-fpDm4oBo6SqLvWUYCmFhdde3U9KH2fRNNMeAnAPAIwxRL345xutL0EtEUcuoxsoazdJGv/MuDBQHlCDrtbvqOg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.4.tgz",
+ "integrity": "sha512-rSJoreDE/HoIzoaib6MTp5jQtCTdMHKIvItAKT/ImS6Y6Ww76oUaeMyp4Vc/fAgd/ehji068IxetHXAnqUwN9A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.4.tgz",
+ "integrity": "sha512-/jm8OGHgn7oGaJu3i/qZI9spUGcJ+y/lk43ttQ/iO1tOd9NissG6o97bighBCiL+BKRngmcDuR6ikfwYdJmVuQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.4.tgz",
+ "integrity": "sha512-tIP06BeD9EqvECBrPZ+sqdPlYrT+aYaAiu1wYziVx5elRK/ftm33JxVDy2bXGbr6J0CrtirCkR87/X5a2euEng==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.4.tgz",
+ "integrity": "sha512-Ql1Q0EQqVThvn9VAVlwNzsUvbSFtCMGjLpRRi4pk5i7NZZ4n5ISiLMjHYtus4VQ2PvkSw24zyaCVsiS+sXPj1w==",
+ "cpu": [
+ "arm64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.4.tgz",
+ "integrity": "sha512-GjbjXD4XXfN19D0LZNbmiCBUoDiRACsYHr0yaIbbn8aFsXjHZifcYqu/W5Er5X2X990WjHXFrxarn5chzItorQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.4.tgz",
+ "integrity": "sha512-p5WR0NOwaRmJ/B1b6IjEFLLivwEsf3PrdBIhRbhTCQisbo2SvHHpG4ELB/+FgQNnB88LTOF86upmJmbvZdQ2lw==",
+ "cpu": [
+ "s390x"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.4.tgz",
+ "integrity": "sha512-4/GyVjmhR+Tc6HLJvwc1sOhPqAZtySiSMesOZyX6JQ5XBxoTDEMKQzvo07NIK6nTon/SivlZqvhzvuVBNQhObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.4.tgz",
+ "integrity": "sha512-l9eeLsCNvPpmSXUej0etw/J1eqV0Jj1D5G/xG6YTijmE6dkv6E2QezgWbTfQk63v952DPqrjOCoiqxq7Bw0YUQ==",
+ "cpu": [
+ "x64"
+ ],
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.4.tgz",
+ "integrity": "sha512-e0F355MSTMm3+UOqtV3L24gFUp2N5m1f8L/7d56deik6va+AXdrt9F8LbzGpeWGWRbZEDq4m8NVnJDeBtf9DZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.4.tgz",
+ "integrity": "sha512-AWLi0uBRYh6QlE7OKhiz+phZC0qwtij2QZmhmOdsLdFn64m7oMpooE9ICE3lhm9xMb4SpDo2WbHcxX1iFLFtqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.4.tgz",
+ "integrity": "sha512-UwSDJOg3dqCAejWdxclJjCsh3Qq4vLYMDxmyHqo1btz3stK2VqgwNd3mm5tuIwzSlGIQ/1H9Hr+Zn09mrezNqQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "peer": true,
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/plugin-babel": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@rolldown/plugin-babel/-/plugin-babel-0.2.3.tgz",
+ "integrity": "sha512-+zEk16yGlz1F9STiRr6uG9hmIXb6nprjLczV/htGptYuLoCuxb+itZ03RKCEeOhBpDDd1NU7qF6x1VLMUp62bw==",
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=22.12.0 || ^24.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.29.0 || ^8.0.0-rc.1",
+ "@babel/plugin-transform-runtime": "^7.29.0 || ^8.0.0-rc.1",
+ "@babel/runtime": "^7.27.0 || ^8.0.0-rc.1",
+ "rolldown": "^1.0.0-rc.5",
+ "vite": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/plugin-transform-runtime": {
+ "optional": true
+ },
+ "@babel/runtime": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
+ "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.2.0.tgz",
+ "integrity": "sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@speed-highlight/core": {
+ "version": "1.2.24",
+ "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.24.tgz",
+ "integrity": "sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==",
+ "license": "CC0-1.0"
+ },
+ "node_modules/@types/gensync": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.5.tgz",
+ "integrity": "sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/jsesc": {
+ "version": "2.5.1",
+ "resolved": "https://registry.npmjs.org/@types/jsesc/-/jsesc-2.5.1.tgz",
+ "integrity": "sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==",
+ "license": "MIT"
+ },
+ "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",
+ "peer": true,
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/agents": {
+ "version": "0.20.1",
+ "resolved": "https://registry.npmjs.org/agents/-/agents-0.20.1.tgz",
+ "integrity": "sha512-HQRYMeZpD3k8djYBH7atRPojZMee3NvmXkzsmMWXfdHZ94vMljmWqSsD1XZd70LovHyQrw6/R81AZZIsRiFM6Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/plugin-proposal-decorators": "^8.0.2",
+ "@cfworker/json-schema": "^4.1.1",
+ "@rolldown/plugin-babel": "^0.2.3",
+ "cron-schedule": "^6.0.0",
+ "esbuild": "^0.28.1",
+ "mimetext": "^3.0.28",
+ "nanoid": "^5.1.16",
+ "partyserver": "^0.5.8",
+ "partysocket": "1.3.0",
+ "yaml": "^2.9.0",
+ "yargs": "^18.0.0"
+ },
+ "bin": {
+ "agents": "dist/cli/index.js"
+ },
+ "peerDependencies": {
+ "@ai-sdk/react": "^3.0.0 || ^4.0.0",
+ "@cloudflare/codemode": ">=0.5.0",
+ "@modelcontextprotocol/client": "2.0.0",
+ "@modelcontextprotocol/sdk": "1.30.0",
+ "@modelcontextprotocol/server": "2.0.0",
+ "@tanstack/ai": ">=0.10.2 <1.0.0",
+ "@x402/core": "^2.0.0",
+ "@x402/evm": "^2.0.0",
+ "ai": "^6.0.0 || ^7.0.0",
+ "chat": "^4.29.0",
+ "just-bash": "^3.0.0",
+ "react": "^19.0.0",
+ "vite": ">=6.0.0 <9.0.0",
+ "zod": "^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@ai-sdk/react": {
+ "optional": true
+ },
+ "@cloudflare/codemode": {
+ "optional": true
+ },
+ "@tanstack/ai": {
+ "optional": true
+ },
+ "@x402/core": {
+ "optional": true
+ },
+ "@x402/evm": {
+ "optional": true
+ },
+ "ai": {
+ "optional": true
+ },
+ "chat": {
+ "optional": true
+ },
+ "just-bash": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ajv": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz",
+ "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "fast-uri": "^3.0.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
+ "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.3.0.tgz",
+ "integrity": "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.11.13",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.13.tgz",
+ "integrity": "sha512-k9HNuUVMlqVjQ9UHzfPjIqiDbWw7WqT1AoT7GL8VwvF3r0ZfArtgiSPAlmupyNquNgOJHTuH4CKYf8ttMTWBTQ==",
+ "license": "Apache-2.0",
+ "peer": true,
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/blake3-wasm": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz",
+ "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==",
+ "license": "MIT"
+ },
+ "node_modules/body-parser": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
+ "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "bytes": "^3.1.2",
+ "content-type": "^2.0.0",
+ "debug": "^4.4.3",
+ "http-errors": "^2.0.1",
+ "iconv-lite": "^0.7.2",
+ "on-finished": "^2.4.1",
+ "qs": "^6.15.2",
+ "raw-body": "^3.0.2",
+ "type-is": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/body-parser/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",
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.8",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.8.tgz",
+ "integrity": "sha512-V2NpofLblG64mfOtSgDhOJESZEGogzDMBv/q+W6oc4LXWP/q75eOXoOaaOu1EOadB9U4Bwx/e0yzbvwKH8zalA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "baseline-browser-mapping": "^2.11.12",
+ "caniuse-lite": "^1.0.30001809",
+ "electron-to-chromium": "^1.5.402",
+ "node-releases": "^2.0.53",
+ "update-browserslist-db": "^1.3.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "license": "MIT",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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/caniuse-lite": {
+ "version": "1.0.30001809",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz",
+ "integrity": "sha512-xxWVywk6a6Arlk+hymeycyn/VgqEfLDxupvhH/xiY5SJ/18kmi9o6MiO320DCUzypORHLtvh0I4i04tUhCNHNQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0",
+ "peer": true
+ },
+ "node_modules/cliui": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-9.0.1.tgz",
+ "integrity": "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^7.2.0",
+ "strip-ansi": "^7.1.0",
+ "wrap-ansi": "^9.0.0"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/cookie": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
+ "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
+ "license": "MIT",
+ "peer": true,
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">=6.6.0"
+ }
+ },
+ "node_modules/core-js-pure": {
+ "version": "3.50.0",
+ "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.50.0.tgz",
+ "integrity": "sha512-6GP3Pxz4IKyWjAfa747vIu/jilB5z29JWROLqH/b+pXVcpgh6tM06ZIBwSuglgVqzDYURhOK6oEzTrG0bCHitA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/core-js"
+ }
+ },
+ "node_modules/cors": {
+ "version": "2.8.6",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
+ "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/cron-schedule": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cron-schedule/-/cron-schedule-6.0.0.tgz",
+ "integrity": "sha512-BoZaseYGXOo5j5HUwTaegIog3JJbuH4BbrY9A1ArLjXpy+RWb3mV28F/9Gv1dDA7E2L8kngWva4NWisnLTyfgQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "peer": true,
+ "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==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/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",
+ "peer": true,
+ "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",
+ "peer": true
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.405",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.405.tgz",
+ "integrity": "sha512-bNglH7lPH5l+yHOes7Zr4VqxhOy4BQ9ZBUX4VdoFgxMpzJk7W1ZoO3Vgd9Pxa9PyjQ76sfm2aKH/nzEcCNRlew==",
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/emoji-regex": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
+ "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
+ "license": "MIT"
+ },
+ "node_modules/empathic": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz",
+ "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/error-stack-parser-es": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz",
+ "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.28.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz",
+ "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.2",
+ "@esbuild/android-arm": "0.28.2",
+ "@esbuild/android-arm64": "0.28.2",
+ "@esbuild/android-x64": "0.28.2",
+ "@esbuild/darwin-arm64": "0.28.2",
+ "@esbuild/darwin-x64": "0.28.2",
+ "@esbuild/freebsd-arm64": "0.28.2",
+ "@esbuild/freebsd-x64": "0.28.2",
+ "@esbuild/linux-arm": "0.28.2",
+ "@esbuild/linux-arm64": "0.28.2",
+ "@esbuild/linux-ia32": "0.28.2",
+ "@esbuild/linux-loong64": "0.28.2",
+ "@esbuild/linux-mips64el": "0.28.2",
+ "@esbuild/linux-ppc64": "0.28.2",
+ "@esbuild/linux-riscv64": "0.28.2",
+ "@esbuild/linux-s390x": "0.28.2",
+ "@esbuild/linux-x64": "0.28.2",
+ "@esbuild/netbsd-arm64": "0.28.2",
+ "@esbuild/netbsd-x64": "0.28.2",
+ "@esbuild/openbsd-arm64": "0.28.2",
+ "@esbuild/openbsd-x64": "0.28.2",
+ "@esbuild/openharmony-arm64": "0.28.2",
+ "@esbuild/sunos-x64": "0.28.2",
+ "@esbuild/win32-arm64": "0.28.2",
+ "@esbuild/win32-ia32": "0.28.2",
+ "@esbuild/win32-x64": "0.28.2"
+ }
+ },
+ "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",
+ "peer": true
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/event-target-polyfill": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/event-target-polyfill/-/event-target-polyfill-0.0.4.tgz",
+ "integrity": "sha512-Gs6RLjzlLRdT8X9ZipJdIZI/Y6/HhRLyq9RdDlCsnpxr/+Nn6bU2EFGuC94GjxqhM+Nmij2Vcq98yoHrU8uNFQ==",
+ "license": "MIT"
+ },
+ "node_modules/eventsource": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz",
+ "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "eventsource-parser": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/eventsource-parser": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.1.1.tgz",
+ "integrity": "sha512-EKN1vKAMcZ8MlYMpaNuxN6R9yakzH6uajHcHVTqWJzvu5pWw9DyhbP35HH8MVBQ+dZjAfDxk+A8NiR9KWaXiyQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=18.0.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",
+ "peer": true,
+ "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/express-rate-limit": {
+ "version": "8.6.2",
+ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.6.2.tgz",
+ "integrity": "sha512-YH4ru+eOJxQABscKFfRCy9R7x9QFGdezclVMwwgFFndzS2Xnm0uo6B0ABZsLhcpeptGv2qvuJVWlQr9gQZoC3A==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "debug": "^4.4.3",
+ "ip-address": "^10.2.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/express-rate-limit"
+ },
+ "peerDependencies": {
+ "express": ">= 4.11"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/fast-uri": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz",
+ "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fastify"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fastify"
+ }
+ ],
+ "license": "BSD-3-Clause",
+ "peer": true
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-east-asian-width": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.6.0.tgz",
+ "integrity": "sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "peer": true,
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "peer": true,
+ "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",
+ "peer": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.13.2",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.2.tgz",
+ "integrity": "sha512-JydRilDRkYBQMt9qR9U92mXxmbGqsqSn/IKOrh4e7/gEbn+0zSr8igTu0obwJoNGN4sez28DIql7FBHWydoJpA==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=16.9.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",
+ "peer": true,
+ "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.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz",
+ "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==",
+ "license": "MIT",
+ "peer": true,
+ "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-meta-resolve": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz",
+ "integrity": "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wooorm"
+ }
+ },
+ "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",
+ "peer": true
+ },
+ "node_modules/ip-address": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.5.0.tgz",
+ "integrity": "sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==",
+ "license": "MIT",
+ "peer": true,
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "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",
+ "peer": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/jose": {
+ "version": "6.2.8",
+ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.8.tgz",
+ "integrity": "sha512-Bsdjwm3Qsd/P0jR+BHDe3LytDfY7WBq2HmCCLIwuVRHMuEC9ae7/R474GIUdF1NgCyZjzVo/A9DOiOBtXq8ZoQ==",
+ "license": "MIT",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/panva"
+ }
+ },
+ "node_modules/js-base64": {
+ "version": "3.9.2",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.9.2.tgz",
+ "integrity": "sha512-6zayE8QlUdiweYI6cETD/XBSqFcoCUlufn/29PJR99r82x1yDnIprRca0YvAYpAW+ez0GuQkVBC6xG5QkD7OjA==",
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/js-tokens": {
+ "version": "10.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
+ "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/json-schema-typed": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz",
+ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==",
+ "license": "BSD-2-Clause",
+ "peer": true
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/kleur": {
+ "version": "4.1.5",
+ "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
+ "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "11.5.2",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz",
+ "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==",
+ "license": "BlueOak-1.0.0",
+ "peer": true,
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz",
+ "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "dependencies": {
+ "mime-db": "^1.54.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/mimetext": {
+ "version": "3.0.28",
+ "resolved": "https://registry.npmjs.org/mimetext/-/mimetext-3.0.28.tgz",
+ "integrity": "sha512-eQXpbNrtxLCjUtiVbR/qR09dbPgZ2o+KR1uA7QKqGhbn8QV7HIL16mXXsobBL4/8TqoYh1us31kfz+dNfCev9g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.26.0",
+ "@babel/runtime-corejs3": "^7.26.0",
+ "js-base64": "^3.7.7",
+ "mime-types": "^2.1.35"
+ },
+ "funding": {
+ "type": "patreon",
+ "url": "https://patreon.com/muratgozel"
+ }
+ },
+ "node_modules/mimetext/node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimetext/node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/miniflare": {
+ "version": "5.20260811.0-alpha",
+ "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-5.20260811.0-alpha.tgz",
+ "integrity": "sha512-sypXsD5fjY88fZNedPqnwrwR1dwfnfbfW7MfvMyIfPJdtRiCCOpUnjWGeFVYYZ+0fQVICye6Juu+vZgzTEx8XA==",
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "0.8.1",
+ "sharp": "0.35.2",
+ "undici": "7.29.0",
+ "workerd": "1.20260811.1",
+ "ws": "8.21.0",
+ "youch": "4.1.0-beta.10"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ }
+ },
+ "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",
+ "peer": true
+ },
+ "node_modules/nanoid": {
+ "version": "5.1.16",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz",
+ "integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.js"
+ },
+ "engines": {
+ "node": "^18 || >=20"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.53",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz",
+ "integrity": "sha512-D9UOmYG3UH1V+ENW56t5QXBwJw1YEY18ruVeus89Rw+SyIgjPkCO84bRzO3uNIYosJbNwiabWVn48o3uJLjxFQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/obug": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz",
+ "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==",
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/partyserver": {
+ "version": "0.5.10",
+ "resolved": "https://registry.npmjs.org/partyserver/-/partyserver-0.5.10.tgz",
+ "integrity": "sha512-t2B3mhTL1IOxCsj8rZgn4TxTuub7oLhypjc1/fGPNsbgnn9OsHms6uR3QEd5bFJ/7xrFo771j3DdUlll8cxgBg==",
+ "license": "ISC",
+ "dependencies": {
+ "nanoid": "^5.1.9"
+ },
+ "peerDependencies": {
+ "@cloudflare/workers-types": "^4.20260424.1 || ^5.20260703.1"
+ }
+ },
+ "node_modules/partysocket": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/partysocket/-/partysocket-1.3.0.tgz",
+ "integrity": "sha512-1zToNyolZFK/7nuAw/K2bZrNzFqaZyRoCEkS+9vG6WSC5ikrN6qWRe96q6ImU51uptz2r+dAwSkwhJVdQi4LiA==",
+ "license": "MIT",
+ "dependencies": {
+ "event-target-polyfill": "^0.0.4"
+ },
+ "peerDependencies": {
+ "react": ">=17"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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",
+ "peer": true,
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "license": "MIT"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC",
+ "peer": true
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkce-challenge": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz",
+ "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=16.20.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",
+ "peer": true,
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.15.3",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz",
+ "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==",
+ "license": "BSD-3-Clause",
+ "peer": true,
+ "dependencies": {
+ "es-define-property": "^1.0.1",
+ "side-channel": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.3.0.tgz",
+ "integrity": "sha512-hek2mFQpPuI4E1BBKrSto+BU3e3x4xuarsbiwr3+lf7p44juvFMV0XFWQAP3xUyqXA4RrXLIoaSUGbSt056ZMw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "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",
+ "peer": true,
+ "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/react": {
+ "version": "19.2.8",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz",
+ "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.4.tgz",
+ "integrity": "sha512-rSr7irW0K7QRWzjdJXqZowkcRdDtjRduh43rBltnVKd0VFq839l1lJoDvGJb6gl7+4rTTCrPWu+YfujUL8Ug7w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@oxc-project/types": "=0.144.0",
+ "@rolldown/pluginutils": "^1.0.0"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.2.4",
+ "@rolldown/binding-darwin-arm64": "1.2.4",
+ "@rolldown/binding-darwin-x64": "1.2.4",
+ "@rolldown/binding-freebsd-x64": "1.2.4",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.2.4",
+ "@rolldown/binding-linux-arm64-gnu": "1.2.4",
+ "@rolldown/binding-linux-arm64-musl": "1.2.4",
+ "@rolldown/binding-linux-ppc64-gnu": "1.2.4",
+ "@rolldown/binding-linux-s390x-gnu": "1.2.4",
+ "@rolldown/binding-linux-x64-gnu": "1.2.4",
+ "@rolldown/binding-linux-x64-musl": "1.2.4",
+ "@rolldown/binding-openharmony-arm64": "1.2.4",
+ "@rolldown/binding-win32-arm64-msvc": "1.2.4",
+ "@rolldown/binding-win32-x64-msvc": "1.2.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",
+ "peer": true,
+ "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/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/semver": {
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
+ "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",
+ "peer": true,
+ "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-static": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz",
+ "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==",
+ "license": "MIT",
+ "peer": true,
+ "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",
+ "peer": true
+ },
+ "node_modules/sharp": {
+ "version": "0.35.2",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz",
+ "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@img/colour": "^1.1.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.8.4"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.35.2",
+ "@img/sharp-darwin-x64": "0.35.2",
+ "@img/sharp-freebsd-wasm32": "0.35.2",
+ "@img/sharp-libvips-darwin-arm64": "1.3.1",
+ "@img/sharp-libvips-darwin-x64": "1.3.1",
+ "@img/sharp-libvips-linux-arm": "1.3.1",
+ "@img/sharp-libvips-linux-arm64": "1.3.1",
+ "@img/sharp-libvips-linux-ppc64": "1.3.1",
+ "@img/sharp-libvips-linux-riscv64": "1.3.1",
+ "@img/sharp-libvips-linux-s390x": "1.3.1",
+ "@img/sharp-libvips-linux-x64": "1.3.1",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.3.1",
+ "@img/sharp-libvips-linuxmusl-x64": "1.3.1",
+ "@img/sharp-linux-arm": "0.35.2",
+ "@img/sharp-linux-arm64": "0.35.2",
+ "@img/sharp-linux-ppc64": "0.35.2",
+ "@img/sharp-linux-riscv64": "0.35.2",
+ "@img/sharp-linux-s390x": "0.35.2",
+ "@img/sharp-linux-x64": "0.35.2",
+ "@img/sharp-linuxmusl-arm64": "0.35.2",
+ "@img/sharp-linuxmusl-x64": "0.35.2",
+ "@img/sharp-webcontainers-wasm32": "0.35.2",
+ "@img/sharp-win32-arm64": "0.35.2",
+ "@img/sharp-win32-ia32": "0.35.2",
+ "@img/sharp-win32-x64": "0.35.2"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "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/statuses": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
+ "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "8.2.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
+ "integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
+ "license": "MIT",
+ "dependencies": {
+ "get-east-asian-width": "^1.5.0",
+ "strip-ansi": "^7.1.2"
+ },
+ "engines": {
+ "node": ">=20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "10.2.2",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz",
+ "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "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",
+ "peer": true,
+ "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",
+ "optional": true
+ },
+ "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",
+ "peer": true,
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/undici": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.0.tgz",
+ "integrity": "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/unenv": {
+ "version": "2.0.0-rc.24",
+ "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz",
+ "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==",
+ "license": "MIT",
+ "dependencies": {
+ "pathe": "^2.0.3"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.1.tgz",
+ "integrity": "sha512-ZZ61DsRsOnakl74HAmp3oSN4aXUmEWXf+i/yv0h7tIBfICc3VdrFErQKUUKPgu3AMsTUMbcongALEN4l6GSUrQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "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",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "peer": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/workerd": {
+ "version": "1.20260811.1",
+ "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20260811.1.tgz",
+ "integrity": "sha512-kh+FFm55JQ4ssxhHZV9VPdMQq3D1nHxNJgwxMtWGD4dGppJvLySdguTRDKgeNTvgq6heSz+6TTXyPSDGj8Yllw==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "workerd": "bin/workerd"
+ },
+ "engines": {
+ "node": ">=16"
+ },
+ "optionalDependencies": {
+ "@cloudflare/workerd-darwin-64": "1.20260811.1",
+ "@cloudflare/workerd-darwin-arm64": "1.20260811.1",
+ "@cloudflare/workerd-linux-64": "1.20260811.1",
+ "@cloudflare/workerd-linux-arm64": "1.20260811.1",
+ "@cloudflare/workerd-windows-64": "1.20260811.1"
+ }
+ },
+ "node_modules/wrangler": {
+ "version": "4.122.0",
+ "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.122.0.tgz",
+ "integrity": "sha512-qkskzgQ76Y1qvVe5JARgvc3RISq6BC2rPoxQhFoKH1dKIwQc3GDFttQ/7m2OfeQ+tmQRzynv2dy/DXnxFCj2Lw==",
+ "license": "MIT OR Apache-2.0",
+ "dependencies": {
+ "@cloudflare/kv-asset-handler": "0.5.0",
+ "@cloudflare/unenv-preset": "2.16.1",
+ "blake3-wasm": "2.1.5",
+ "esbuild": "0.28.1",
+ "miniflare": "5.20260811.0-alpha",
+ "path-to-regexp": "6.3.0",
+ "unenv": "2.0.0-rc.24",
+ "workerd": "1.20260811.1"
+ },
+ "bin": {
+ "cf-wrangler": "bin/cf-wrangler.js",
+ "wrangler": "bin/wrangler.js",
+ "wrangler2": "bin/wrangler.js"
+ },
+ "engines": {
+ "node": ">=22.0.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.3"
+ },
+ "peerDependencies": {
+ "@cloudflare/workers-types": "^5.20260811.1"
+ },
+ "peerDependenciesMeta": {
+ "@cloudflare/workers-types": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz",
+ "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/android-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz",
+ "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/android-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz",
+ "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/android-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz",
+ "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz",
+ "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/darwin-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz",
+ "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-arm": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz",
+ "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz",
+ "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz",
+ "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-loong64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz",
+ "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==",
+ "cpu": [
+ "loong64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz",
+ "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==",
+ "cpu": [
+ "mips64el"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz",
+ "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz",
+ "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-s390x": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz",
+ "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/linux-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz",
+ "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz",
+ "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz",
+ "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz",
+ "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/sunos-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz",
+ "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/win32-arm64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz",
+ "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/win32-ia32": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz",
+ "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/@esbuild/win32-x64": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz",
+ "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/wrangler/node_modules/esbuild": {
+ "version": "0.28.1",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz",
+ "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.28.1",
+ "@esbuild/android-arm": "0.28.1",
+ "@esbuild/android-arm64": "0.28.1",
+ "@esbuild/android-x64": "0.28.1",
+ "@esbuild/darwin-arm64": "0.28.1",
+ "@esbuild/darwin-x64": "0.28.1",
+ "@esbuild/freebsd-arm64": "0.28.1",
+ "@esbuild/freebsd-x64": "0.28.1",
+ "@esbuild/linux-arm": "0.28.1",
+ "@esbuild/linux-arm64": "0.28.1",
+ "@esbuild/linux-ia32": "0.28.1",
+ "@esbuild/linux-loong64": "0.28.1",
+ "@esbuild/linux-mips64el": "0.28.1",
+ "@esbuild/linux-ppc64": "0.28.1",
+ "@esbuild/linux-riscv64": "0.28.1",
+ "@esbuild/linux-s390x": "0.28.1",
+ "@esbuild/linux-x64": "0.28.1",
+ "@esbuild/netbsd-arm64": "0.28.1",
+ "@esbuild/netbsd-x64": "0.28.1",
+ "@esbuild/openbsd-arm64": "0.28.1",
+ "@esbuild/openbsd-x64": "0.28.1",
+ "@esbuild/openharmony-arm64": "0.28.1",
+ "@esbuild/sunos-x64": "0.28.1",
+ "@esbuild/win32-arm64": "0.28.1",
+ "@esbuild/win32-ia32": "0.28.1",
+ "@esbuild/win32-x64": "0.28.1"
+ }
+ },
+ "node_modules/wrangler/node_modules/path-to-regexp": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
+ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==",
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi": {
+ "version": "9.0.2",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
+ "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.2.1",
+ "string-width": "^7.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
+ "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^10.3.0",
+ "get-east-asian-width": "^1.0.0",
+ "strip-ansi": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "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",
+ "peer": true
+ },
+ "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/yaml": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
+ "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
+ "license": "ISC",
+ "bin": {
+ "yaml": "bin.mjs"
+ },
+ "engines": {
+ "node": ">= 14.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/eemeli"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "18.1.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-18.1.0.tgz",
+ "integrity": "sha512-2rAgRKu54VsHkqI0/tYkmluGXHD4KW7yZoycuqDQ15QOTnc2VVfy0nN/1eMhnQLO00A+dwtK20xuCnc1YGeUyg==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^9.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "string-width": "^8.2.1",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^22.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "22.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-22.0.0.tgz",
+ "integrity": "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw==",
+ "license": "ISC",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=23"
+ }
+ },
+ "node_modules/youch": {
+ "version": "4.1.0-beta.10",
+ "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz",
+ "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@poppinss/colors": "^4.1.5",
+ "@poppinss/dumper": "^0.6.4",
+ "@speed-highlight/core": "^1.2.7",
+ "cookie": "^1.0.2",
+ "youch-core": "^0.3.3"
+ }
+ },
+ "node_modules/youch-core": {
+ "version": "0.3.3",
+ "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz",
+ "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==",
+ "license": "MIT",
+ "dependencies": {
+ "@poppinss/exception": "^1.2.2",
+ "error-stack-parser-es": "^1.0.5"
+ }
+ },
+ "node_modules/youch/node_modules/cookie": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
+ "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
+ "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.25.2",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz",
+ "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==",
+ "license": "ISC",
+ "peer": true,
+ "peerDependencies": {
+ "zod": "^3.25.28 || ^4"
+ }
+ }
+ }
+}
diff --git a/workers/mcp/package.json b/workers/mcp/package.json
new file mode 100644
index 0000000..8865748
--- /dev/null
+++ b/workers/mcp/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "openfindata-mcp",
+ "private": true,
+ "version": "0.3.1",
+ "type": "module",
+ "scripts": {
+ "dev": "wrangler dev",
+ "deploy": "wrangler deploy",
+ "types": "wrangler types"
+ },
+ "dependencies": {
+ "@cloudflare/workers-types": "^5.20260813.1",
+ "@modelcontextprotocol/server": "^2.0.0",
+ "agents": "^0.20.1",
+ "wrangler": "^4.122.0",
+ "zod": "^4.4.3"
+ }
+}
diff --git a/workers/mcp/public/index.html b/workers/mcp/public/index.html
new file mode 100644
index 0000000..4e6f193
--- /dev/null
+++ b/workers/mcp/public/index.html
@@ -0,0 +1,57 @@
+
+
+
+
+
+ openfindata — MCP público
+
+
+
+
+
+ Dados Financeiros Abertos · alpha
+ MCP público no Cloudflare Workers
+
+ Superfície pública: /mcp (Streamable HTTP).
+ REST/FastAPI, CLI e code mode não ficam na internet — só na rede Tailscale.
+
+ Tools neste Worker (fontes JSON públicas, sem processar Python):
+
+ bcb_series bcb_ptax bcb_focus
+ ibge_indicator ibge_ipca_breakdown
+ ipea_series ipea_search
+ tesouro_siconfi
+ openfinance_directory
+
+
+ CVM/B3/ANBIMA/registry e a API REST completa continuam no pacote Python
+ (pip install openfindata) e no FastAPI interno.
+
+ Cliente MCP:
+ {
+ "mcpServers": {
+ "openfindata": {
+ "url": "https://openfindata.com.br/mcp"
+ }
+ }
+}
+
+ Código:
+ github.com/robertoecf/OpenFinData
+
+
+
+
diff --git a/workers/mcp/src/catalog/focus.json b/workers/mcp/src/catalog/focus.json
new file mode 100644
index 0000000..97c3b4d
--- /dev/null
+++ b/workers/mcp/src/catalog/focus.json
@@ -0,0 +1,17 @@
+[
+ "IPCA",
+ "IGP-DI",
+ "IGP-M",
+ "INPC",
+ "IPA-DI",
+ "IPA-M",
+ "Câmbio",
+ "PIB Total",
+ "Produção industrial",
+ "Selic",
+ "Taxa de desocupação",
+ "Balança comercial",
+ "Conta corrente",
+ "Investimento direto no país",
+ "Dívida líquida do setor público"
+]
diff --git a/workers/mcp/src/catalog/ibge.json b/workers/mcp/src/catalog/ibge.json
new file mode 100644
index 0000000..8f98209
--- /dev/null
+++ b/workers/mcp/src/catalog/ibge.json
@@ -0,0 +1,51 @@
+{
+ "indicators": {
+ "ipca_mensal": {
+ "agregado": 7060,
+ "variavel": 63,
+ "description": "IPCA variação mensal por grupo",
+ "classificacao": "315"
+ },
+ "ipca_acumulado_ano": {
+ "agregado": 7060,
+ "variavel": 69,
+ "description": "IPCA acumulado no ano por grupo",
+ "classificacao": "315"
+ },
+ "ipca_acumulado_12m": {
+ "agregado": 7060,
+ "variavel": 2265,
+ "description": "IPCA acumulado 12 meses por grupo",
+ "classificacao": "315"
+ },
+ "ipca_peso": {
+ "agregado": 7060,
+ "variavel": 66,
+ "description": "IPCA peso mensal por grupo",
+ "classificacao": "315"
+ },
+ "inpc_mensal": {
+ "agregado": 7063,
+ "variavel": 44,
+ "description": "INPC variação mensal",
+ "classificacao": "315"
+ },
+ "pib_trimestral": {
+ "agregado": 5932,
+ "variavel": 6561,
+ "description": "PIB taxa acumulada em 4 trimestres"
+ }
+ },
+ "ipca_groups": {
+ "7169": "Índice geral",
+ "7170": "1.Alimentação e bebidas",
+ "7445": "2.Habitação",
+ "7486": "3.Artigos de residência",
+ "7558": "4.Vestuário",
+ "7625": "5.Transportes",
+ "7660": "6.Saúde e cuidados pessoais",
+ "7712": "7.Despesas pessoais",
+ "7766": "8.Educação",
+ "7786": "9.Comunicação"
+ }
+}
diff --git a/workers/mcp/src/catalog/ipea.json b/workers/mcp/src/catalog/ipea.json
new file mode 100644
index 0000000..a6c1961
--- /dev/null
+++ b/workers/mcp/src/catalog/ipea.json
@@ -0,0 +1,38 @@
+{
+ "selic_over_mensal": {
+ "code": "BM12_TJOVER12",
+ "description": "Taxa Selic over acumulada no mês",
+ "unidade": "% a.m.",
+ "periodicidade": "mensal"
+ },
+ "ipca_anual_ipea": {
+ "code": "PAN12_IPCAG12",
+ "description": "IPCA acumulado 12 meses (IPEA)",
+ "unidade": "% a.a.",
+ "periodicidade": "mensal"
+ },
+ "pib_real_anual": {
+ "code": "SCN10_PIBG10",
+ "description": "PIB - variação real anual (IBGE/SCN)",
+ "unidade": "% a.a.",
+ "periodicidade": "anual"
+ },
+ "desemprego_pme": {
+ "code": "PME12_TDESOC12",
+ "description": "Taxa de desocupação - PME (série longa, descontinuada em 2016)",
+ "unidade": "%",
+ "periodicidade": "mensal"
+ },
+ "salario_minimo_real": {
+ "code": "GAC12_SALMINRE12",
+ "description": "Salário mínimo real (base=jul/1994)",
+ "unidade": "R$",
+ "periodicidade": "mensal"
+ },
+ "divida_externa_pib": {
+ "code": "BM12_DEXTT12",
+ "description": "Dívida externa / PIB",
+ "unidade": "% do PIB",
+ "periodicidade": "mensal"
+ }
+}
diff --git a/workers/mcp/src/catalog/sgs.json b/workers/mcp/src/catalog/sgs.json
new file mode 100644
index 0000000..fafd335
--- /dev/null
+++ b/workers/mcp/src/catalog/sgs.json
@@ -0,0 +1,428 @@
+{
+ "selic": {
+ "code": 432,
+ "name": "Taxa Selic",
+ "unit": "% a.a.",
+ "freq": "diária"
+ },
+ "selic_meta": {
+ "code": 4189,
+ "name": "Taxa Selic Meta",
+ "unit": "% a.a.",
+ "freq": "diária"
+ },
+ "cdi": {
+ "code": 12,
+ "name": "Taxa CDI",
+ "unit": "% a.a.",
+ "freq": "diária"
+ },
+ "cdi_acum_mensal": {
+ "code": 4389,
+ "name": "CDI acumulado mensal",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "cdi_mensal": {
+ "code": 4391,
+ "name": "CDI mensal",
+ "unit": "% a.m.",
+ "freq": "mensal"
+ },
+ "tr": {
+ "code": 226,
+ "name": "Taxa Referencial (TR)",
+ "unit": "%",
+ "freq": "diária"
+ },
+ "tjlp": {
+ "code": 256,
+ "name": "Taxa de Juros de Longo Prazo (TJLP)",
+ "unit": "% a.a.",
+ "freq": "mensal"
+ },
+ "tlp": {
+ "code": 27572,
+ "name": "Taxa de Longo Prazo (TLP)",
+ "unit": "% a.a.",
+ "freq": "mensal"
+ },
+ "cdb": {
+ "code": 3946,
+ "name": "Taxa média de CDB pré-fixado (histórico, descontinuado em 2012)",
+ "unit": "% a.a.",
+ "freq": "mensal"
+ },
+ "poupanca": {
+ "code": 195,
+ "name": "Rendimento poupança",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "dolar_ptax": {
+ "code": 1,
+ "name": "Dólar PTAX venda",
+ "unit": "BRL/USD",
+ "freq": "diária"
+ },
+ "dolar_compra": {
+ "code": 10813,
+ "name": "Dólar PTAX compra",
+ "unit": "BRL/USD",
+ "freq": "diária"
+ },
+ "euro": {
+ "code": 21619,
+ "name": "Euro PTAX venda",
+ "unit": "BRL/EUR",
+ "freq": "diária"
+ },
+ "libra": {
+ "code": 21623,
+ "name": "Libra esterlina PTAX venda",
+ "unit": "BRL/GBP",
+ "freq": "diária"
+ },
+ "iene": {
+ "code": 21625,
+ "name": "Iene PTAX venda",
+ "unit": "BRL/JPY",
+ "freq": "diária"
+ },
+ "franco_suico": {
+ "code": 21624,
+ "name": "Franco suíço PTAX venda",
+ "unit": "BRL/CHF",
+ "freq": "diária"
+ },
+ "ipca": {
+ "code": 433,
+ "name": "IPCA mensal",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_12m": {
+ "code": 13522,
+ "name": "IPCA acumulado 12 meses",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_15": {
+ "code": 7478,
+ "name": "IPCA-15 mensal",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_e": {
+ "code": 1635,
+ "name": "IPCA-E (especial) mensal",
+ "unit": "%",
+ "freq": "trimestral"
+ },
+ "ipca_e_acum": {
+ "code": 1638,
+ "name": "IPCA-E acumulado no trimestre",
+ "unit": "%",
+ "freq": "trimestral"
+ },
+ "igpm": {
+ "code": 189,
+ "name": "IGP-M mensal",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "igpdi": {
+ "code": 190,
+ "name": "IGP-DI mensal",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "igp10": {
+ "code": 7448,
+ "name": "IGP-10 mensal",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "inpc": {
+ "code": 188,
+ "name": "INPC mensal",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipc_fipe": {
+ "code": 193,
+ "name": "IPC-FIPE mensal",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_livres": {
+ "code": 11428,
+ "name": "IPCA preços livres",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_monitorados": {
+ "code": 4449,
+ "name": "IPCA preços monitorados",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_servicos": {
+ "code": 10844,
+ "name": "IPCA serviços",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_bens": {
+ "code": 10764,
+ "name": "IPCA bens não-duráveis",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_nucleo_ms": {
+ "code": 4466,
+ "name": "IPCA núcleo por médias aparadas com suavização",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "ipca_nucleo_ma": {
+ "code": 16121,
+ "name": "IPCA núcleo por médias aparadas sem suavização",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "pib_mensal": {
+ "code": 4380,
+ "name": "PIB mensal (IBC-Br)",
+ "unit": "índice",
+ "freq": "mensal"
+ },
+ "ibcbr": {
+ "code": 24364,
+ "name": "IBC-Br dessazonalizado",
+ "unit": "índice",
+ "freq": "mensal"
+ },
+ "ibcbr_agro": {
+ "code": 27574,
+ "name": "IBC-Br setor agropecuário",
+ "unit": "índice",
+ "freq": "mensal"
+ },
+ "ibcbr_servicos": {
+ "code": 27576,
+ "name": "IBC-Br setor serviços",
+ "unit": "índice",
+ "freq": "mensal"
+ },
+ "producao_industrial": {
+ "code": 21859,
+ "name": "Produção industrial geral (IBGE)",
+ "unit": "índice",
+ "freq": "mensal"
+ },
+ "capacidade_ociosa": {
+ "code": 1344,
+ "name": "Utilização da capacidade instalada na indústria (FGV)",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "desemprego": {
+ "code": 24369,
+ "name": "Taxa de desocupação (PNAD Contínua)",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "vendas_varejo": {
+ "code": 1455,
+ "name": "Vendas no varejo (volume)",
+ "unit": "índice",
+ "freq": "mensal"
+ },
+ "icc_fgv": {
+ "code": 4393,
+ "name": "Índice de Confiança do Consumidor (FGV)",
+ "unit": "índice",
+ "freq": "mensal"
+ },
+ "icei_cni": {
+ "code": 7341,
+ "name": "Índice de Confiança do Empresário Industrial (CNI)",
+ "unit": "índice",
+ "freq": "mensal"
+ },
+ "m1": {
+ "code": 1828,
+ "name": "Meios de pagamento M1 (saldo em final de período)",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "m2": {
+ "code": 1832,
+ "name": "Meios de pagamento M2 (saldo em final de período)",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "m3": {
+ "code": 1831,
+ "name": "Meios de pagamento M3 (saldo em final de período)",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "m4": {
+ "code": 1833,
+ "name": "Meios de pagamento M4 (saldo em final de período)",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "base_monetaria": {
+ "code": 1788,
+ "name": "Base monetária (saldo em final de período)",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "papel_moeda": {
+ "code": 1786,
+ "name": "Papel-moeda emitido (saldo em final de período)",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "credito_total": {
+ "code": 20539,
+ "name": "Saldo de crédito do SFN — total",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "credito_pf": {
+ "code": 20570,
+ "name": "Saldo de crédito do SFN — pessoas físicas",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "credito_pj": {
+ "code": 20543,
+ "name": "Saldo de crédito do SFN — pessoas jurídicas",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "inadimplencia_total": {
+ "code": 21082,
+ "name": "Inadimplência da carteira — total",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "inadimplencia_pf": {
+ "code": 21084,
+ "name": "Inadimplência da carteira — pessoas físicas",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "inadimplencia_pj": {
+ "code": 21083,
+ "name": "Inadimplência da carteira — pessoas jurídicas",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "spread_medio": {
+ "code": 20783,
+ "name": "Spread médio das operações de crédito",
+ "unit": "p.p.",
+ "freq": "mensal"
+ },
+ "taxa_credito_pf": {
+ "code": 20741,
+ "name": "Taxa média de juros — crédito PF",
+ "unit": "% a.a.",
+ "freq": "mensal"
+ },
+ "taxa_credito_pj": {
+ "code": 20714,
+ "name": "Taxa média de juros — crédito PJ",
+ "unit": "% a.a.",
+ "freq": "mensal"
+ },
+ "endividamento_familias": {
+ "code": 21379,
+ "name": "Endividamento das famílias (% renda 12m)",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "balanca_comercial": {
+ "code": 22707,
+ "name": "Balança comercial — saldo mensal",
+ "unit": "US$ milhões",
+ "freq": "mensal"
+ },
+ "exportacoes": {
+ "code": 22708,
+ "name": "Exportações — total mensal",
+ "unit": "US$ milhões",
+ "freq": "mensal"
+ },
+ "importacoes": {
+ "code": 22709,
+ "name": "Importações — total mensal",
+ "unit": "US$ milhões",
+ "freq": "mensal"
+ },
+ "conta_corrente": {
+ "code": 22701,
+ "name": "Transações correntes — saldo mensal",
+ "unit": "US$ milhões",
+ "freq": "mensal"
+ },
+ "idp": {
+ "code": 22865,
+ "name": "Investimento Direto no País (IDP) — ingressos líquidos",
+ "unit": "US$ milhões",
+ "freq": "mensal"
+ },
+ "reservas_internacionais": {
+ "code": 3546,
+ "name": "Reservas internacionais — conceito liquidez (mensal)",
+ "unit": "US$ milhões",
+ "freq": "mensal"
+ },
+ "reservas_diaria": {
+ "code": 13621,
+ "name": "Reservas internacionais — conceito liquidez (diária)",
+ "unit": "US$ milhões",
+ "freq": "diária"
+ },
+ "divida_pib": {
+ "code": 4513,
+ "name": "Dívida líquida setor público / PIB",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "dbgg_pib": {
+ "code": 13762,
+ "name": "Dívida bruta governo geral (DBGG) / PIB",
+ "unit": "%",
+ "freq": "mensal"
+ },
+ "dbgg_saldo": {
+ "code": 4502,
+ "name": "Dívida bruta governo geral (DBGG) — saldo",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "resultado_primario": {
+ "code": 4649,
+ "name": "Resultado primário do setor público — fluxo mensal",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "resultado_nominal": {
+ "code": 4647,
+ "name": "Resultado nominal do setor público — fluxo mensal",
+ "unit": "R$ milhões",
+ "freq": "mensal"
+ },
+ "juros_nominais_pib": {
+ "code": 5727,
+ "name": "Juros nominais do setor público — % do PIB acum. 12m",
+ "unit": "% PIB",
+ "freq": "mensal"
+ }
+}
diff --git a/workers/mcp/src/index.ts b/workers/mcp/src/index.ts
new file mode 100644
index 0000000..be27f80
--- /dev/null
+++ b/workers/mcp/src/index.ts
@@ -0,0 +1,22 @@
+import { createMcpHandler } from "agents/mcp/server";
+import { createServer } from "./server";
+
+const mcp = createMcpHandler(createServer, { route: "/mcp" });
+
+export default {
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise {
+ const url = new URL(request.url);
+ if (url.pathname === "/health") {
+ return Response.json({
+ status: "ok",
+ surface: "mcp-worker",
+ version: "0.3.1",
+ mcp: "/mcp",
+ });
+ }
+ if (url.pathname === "/mcp" || url.pathname.startsWith("/mcp/")) {
+ return mcp(request, env, ctx);
+ }
+ return env.ASSETS.fetch(request);
+ },
+} satisfies ExportedHandler;
diff --git a/workers/mcp/src/lib/http.ts b/workers/mcp/src/lib/http.ts
new file mode 100644
index 0000000..2e89446
--- /dev/null
+++ b/workers/mcp/src/lib/http.ts
@@ -0,0 +1,79 @@
+const USER_AGENT = "openfindata-mcp/0.3.1 (+https://github.com/robertoecf/OpenFinData)";
+
+export const FETCH_TIMEOUT_MS = 15_000;
+export const MAX_RESPONSE_BYTES = 2_000_000;
+
+export class UpstreamError extends Error {
+ constructor(
+ readonly status: number,
+ readonly url: string,
+ ) {
+ super(`upstream ${status} for ${url}`);
+ this.name = "UpstreamError";
+ }
+}
+
+export class UpstreamLimitError extends Error {
+ constructor(message: string) {
+ super(message);
+ this.name = "UpstreamLimitError";
+ }
+}
+
+export type GetJsonOptions = {
+ maxBytes?: number;
+ timeoutMs?: number;
+};
+
+export async function getJson(
+ url: string,
+ params?: Record,
+ options?: GetJsonOptions,
+): Promise {
+ const target = new URL(url);
+ if (params) {
+ for (const [key, value] of Object.entries(params)) {
+ if (value !== undefined && value !== "") {
+ target.searchParams.set(key, String(value));
+ }
+ }
+ }
+ const maxBytes = options?.maxBytes ?? MAX_RESPONSE_BYTES;
+ const timeoutMs = options?.timeoutMs ?? FETCH_TIMEOUT_MS;
+ const response = await fetch(target, {
+ headers: { accept: "application/json", "user-agent": USER_AGENT },
+ signal: AbortSignal.timeout(timeoutMs),
+ });
+ if (!response.ok) {
+ throw new UpstreamError(response.status, target.toString());
+ }
+ const buffer = await response.arrayBuffer();
+ if (buffer.byteLength > maxBytes) {
+ throw new UpstreamLimitError(
+ `upstream response too large (${buffer.byteLength} bytes, max ${maxBytes})`,
+ );
+ }
+ return JSON.parse(new TextDecoder().decode(buffer)) as unknown;
+}
+
+export function odataValue(raw: unknown): unknown[] {
+ if (Array.isArray(raw)) {
+ return raw;
+ }
+ if (raw && typeof raw === "object" && "value" in raw) {
+ const value = (raw as { value: unknown }).value;
+ return Array.isArray(value) ? value : [];
+ }
+ return [];
+}
+
+export function jsonResult(data: unknown): { content: [{ type: "text"; text: string }] } {
+ return { content: [{ type: "text", text: JSON.stringify(data) }] };
+}
+
+export function errorResult(message: string): {
+ isError: true;
+ content: [{ type: "text"; text: string }];
+} {
+ return { isError: true, content: [{ type: "text", text: message }] };
+}
diff --git a/workers/mcp/src/server.ts b/workers/mcp/src/server.ts
new file mode 100644
index 0000000..08507d1
--- /dev/null
+++ b/workers/mcp/src/server.ts
@@ -0,0 +1,156 @@
+import { McpServer } from "@modelcontextprotocol/server";
+import { z } from "zod";
+import { errorResult } from "./lib/http";
+import { bcbFocus, bcbPtax, bcbSeries } from "./tools/bcb";
+import { ibgeIndicator, ibgeIpcaBreakdown } from "./tools/ibge";
+import { ipeaSearch, ipeaSeries } from "./tools/ipea";
+import { tesouroSiconfi } from "./tools/tesouro";
+import { openfinanceDirectory } from "./tools/openfinance";
+
+type ToolResult = { content: [{ type: "text"; text: string }]; isError?: boolean };
+
+function wrap(run: (args: T) => Promise) {
+ return async (args: T) => {
+ try {
+ return await run(args);
+ } catch (error) {
+ const message = error instanceof Error ? error.message : String(error);
+ return errorResult(message);
+ }
+ };
+}
+
+export function createServer() {
+ const server = new McpServer({
+ name: "openfindata",
+ version: "0.3.1",
+ websiteUrl: "https://openfindata.com.br",
+ });
+
+ server.registerTool(
+ "bcb_series",
+ {
+ description:
+ "BCB time series (Selic, IPCA, câmbio…): omit args to list the catalog; pass code or name to fetch.",
+ inputSchema: {
+ code: z.number().int().optional(),
+ name: z.string().optional(),
+ start: z.string().optional().describe("YYYY-MM-DD"),
+ end: z.string().optional().describe("YYYY-MM-DD"),
+ last_n: z.number().int().min(1).max(200).optional(),
+ },
+ },
+ wrap((args) => bcbSeries(args)),
+ );
+
+ server.registerTool(
+ "bcb_ptax",
+ {
+ description: "PTAX official exchange rate. Range queries are USD-only.",
+ inputSchema: {
+ currency: z.string().default("USD"),
+ date: z.string().optional().describe("YYYY-MM-DD"),
+ start: z.string().optional(),
+ end: z.string().optional(),
+ },
+ },
+ wrap((args) => bcbPtax(args)),
+ );
+
+ server.registerTool(
+ "bcb_focus",
+ {
+ description:
+ "Boletim Focus. indicator=list for names; indicator=Selic for COPOM path; else annual/monthly.",
+ inputSchema: {
+ indicator: z.string().default("IPCA"),
+ horizon: z.enum(["annual", "monthly"]).default("annual"),
+ panel: z.enum(["market", "top5"]).default("market"),
+ top: z.number().int().min(1).max(100).default(20),
+ },
+ },
+ wrap((args) => bcbFocus(args)),
+ );
+
+ server.registerTool(
+ "ibge_indicator",
+ {
+ description: "IBGE economic indicators. Omit name to list the catalog.",
+ inputSchema: {
+ name: z.string().optional(),
+ periods: z.number().int().min(1).max(120).default(12),
+ },
+ },
+ wrap((args) => ibgeIndicator(args)),
+ );
+
+ server.registerTool(
+ "ibge_ipca_breakdown",
+ {
+ description: "IPCA monthly variation by major groups (not in BCB SGS).",
+ inputSchema: {
+ periods: z.number().int().min(1).max(60).default(6),
+ },
+ },
+ wrap((args) => ibgeIpcaBreakdown(args)),
+ );
+
+ server.registerTool(
+ "ipea_series",
+ {
+ description: "IPEA series. Omit sercodigo for the curated catalog.",
+ inputSchema: {
+ sercodigo: z.string().optional(),
+ dataset: z.enum(["values", "metadata"]).default("values"),
+ top: z.number().int().min(1).max(500).optional(),
+ },
+ },
+ wrap((args) => ipeaSeries(args)),
+ );
+
+ server.registerTool(
+ "ipea_search",
+ {
+ description: "Full-text search across the IPEA catalog (~8k series).",
+ inputSchema: {
+ q: z.string().min(2),
+ top: z.number().int().min(1).max(200).default(25),
+ },
+ },
+ wrap((args) => ipeaSearch(args)),
+ );
+
+ server.registerTool(
+ "tesouro_siconfi",
+ {
+ description: "SICONFI public-finance reports. Start with report=entes.",
+ inputSchema: {
+ report: z.enum(["rreo", "rgf", "entes"]).default("entes"),
+ year: z.number().int().min(2013).optional(),
+ period: z.number().int().min(1).max(6).optional(),
+ cod_ibge: z.number().int().optional(),
+ poder: z.string().default("E"),
+ anexo: z.string().optional(),
+ },
+ },
+ wrap((args) => tesouroSiconfi(args)),
+ );
+
+ server.registerTool(
+ "openfinance_directory",
+ {
+ description: "Open Finance Brasil Directory (public discovery only, no customer data).",
+ inputSchema: {
+ dataset: z.enum(["participants", "endpoints", "resources", "roles"]).default("participants"),
+ role: z.string().optional(),
+ status: z.string().optional(),
+ api_family: z.string().optional(),
+ q: z.string().min(2).optional(),
+ limit: z.number().int().min(1).max(1000).default(100),
+ },
+ },
+ wrap((args) => openfinanceDirectory(args)),
+ );
+
+ return server;
+}
diff --git a/workers/mcp/src/tools/bcb.ts b/workers/mcp/src/tools/bcb.ts
new file mode 100644
index 0000000..ca23cd2
--- /dev/null
+++ b/workers/mcp/src/tools/bcb.ts
@@ -0,0 +1,160 @@
+import { errorResult, getJson, jsonResult, odataValue } from "../lib/http";
+import sgsCatalog from "../catalog/sgs.json";
+import focusIndicators from "../catalog/focus.json";
+
+const SGS_URL = "https://api.bcb.gov.br/dados/serie/bcdata.sgs.{code}/dados";
+const PTAX_URL = "https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata";
+const FOCUS_URL = "https://olinda.bcb.gov.br/olinda/servico/Expectativas/versao/v1/odata";
+
+type SgsPoint = { data: string; valor: number };
+
+function parseSgs(raw: unknown): SgsPoint[] {
+ if (!Array.isArray(raw)) {
+ return [];
+ }
+ const out: SgsPoint[] = [];
+ for (const item of raw) {
+ if (!item || typeof item !== "object") continue;
+ const row = item as { data?: unknown; valor?: unknown };
+ if (typeof row.data !== "string") continue;
+ const valor = Number(row.valor);
+ if (Number.isNaN(valor)) continue;
+ out.push({ data: row.data, valor });
+ }
+ return out;
+}
+
+function fmtPtaxDate(iso: string): string {
+ const [year, month, day] = iso.split("-");
+ return `${month}-${day}-${year}`;
+}
+
+function fmtSgsDate(iso: string): string {
+ const [year, month, day] = iso.split("-");
+ return `${day}/${month}/${year}`;
+}
+
+export async function bcbSeries(args: {
+ code?: number;
+ name?: string;
+ start?: string;
+ end?: string;
+ last_n?: number;
+}) {
+ if (args.code === undefined && !args.name) {
+ return jsonResult(sgsCatalog);
+ }
+ if (args.name) {
+ const entry = (sgsCatalog as Record)[args.name];
+ if (!entry) {
+ return errorResult(`unknown series '${args.name}'`);
+ }
+ const n = Math.min(args.last_n ?? 10, 200);
+ const raw = await getJson(`${SGS_URL.replace("{code}", String(entry.code))}/ultimos/${n}`, {
+ formato: "json",
+ });
+ return jsonResult(parseSgs(raw));
+ }
+ const code = args.code as number;
+ const bounded = args.last_n ?? (args.start && args.end ? undefined : 10);
+ if (bounded !== undefined) {
+ const raw = await getJson(
+ `${SGS_URL.replace("{code}", String(code))}/ultimos/${Math.min(bounded, 200)}`,
+ {
+ formato: "json",
+ },
+ );
+ return jsonResult(parseSgs(raw));
+ }
+ const raw = await getJson(SGS_URL.replace("{code}", String(code)), {
+ formato: "json",
+ dataInicial: args.start ? fmtSgsDate(args.start) : undefined,
+ dataFinal: args.end ? fmtSgsDate(args.end) : undefined,
+ });
+ return jsonResult(parseSgs(raw));
+}
+
+export async function bcbPtax(args: {
+ currency?: string;
+ date?: string;
+ start?: string;
+ end?: string;
+}) {
+ const currency = (args.currency ?? "USD").toUpperCase();
+ if (args.start && args.end) {
+ if (currency !== "USD") {
+ return errorResult("range queries are USD-only; use `date` for other currencies");
+ }
+ const raw = await getJson(
+ `${PTAX_URL}/CotacaoDolarPeriodo(dataInicial=@dataInicial,dataFinalCotacao=@dataFinalCotacao)`,
+ {
+ "@dataInicial": `'${fmtPtaxDate(args.start)}'`,
+ "@dataFinalCotacao": `'${fmtPtaxDate(args.end)}'`,
+ $format: "json",
+ },
+ );
+ return jsonResult(odataValue(raw));
+ }
+ const day = args.date ?? new Date().toISOString().slice(0, 10);
+ if (currency === "USD") {
+ const raw = await getJson(`${PTAX_URL}/CotacaoDolarDia(dataCotacao=@dataCotacao)`, {
+ "@dataCotacao": `'${fmtPtaxDate(day)}'`,
+ $format: "json",
+ });
+ return jsonResult(odataValue(raw));
+ }
+ const raw = await getJson(`${PTAX_URL}/CotacaoMoedaDia(moeda=@moeda,dataCotacao=@dataCotacao)`, {
+ "@moeda": `'${currency}'`,
+ "@dataCotacao": `'${fmtPtaxDate(day)}'`,
+ $format: "json",
+ });
+ return jsonResult(odataValue(raw));
+}
+
+function focusIndicator(name: string): string | undefined {
+ const list = focusIndicators as string[];
+ return list.find((item) => item.toUpperCase() === name.toUpperCase());
+}
+
+export async function bcbFocus(args: {
+ indicator?: string;
+ horizon?: "annual" | "monthly";
+ panel?: "market" | "top5";
+ top?: number;
+}) {
+ const indicator = args.indicator ?? "IPCA";
+ const top = args.top ?? 20;
+ if (indicator.trim().toLowerCase() === "list") {
+ return jsonResult(focusIndicators);
+ }
+ if (indicator.trim().toLowerCase() === "selic") {
+ const raw = await getJson(`${FOCUS_URL}/ExpectativasMercadoSelic`, {
+ $top: top,
+ $format: "json",
+ $orderby: "Data desc",
+ });
+ return jsonResult(odataValue(raw));
+ }
+ const safe = focusIndicator(indicator);
+ if (!safe) {
+ return errorResult(`unknown indicator '${indicator}'`);
+ }
+ const horizon = args.horizon ?? "annual";
+ const panel = args.panel ?? "market";
+ if (panel === "top5" && horizon === "monthly") {
+ return errorResult("panel=top5 is annual-only; use horizon=annual");
+ }
+ const endpoint =
+ panel === "top5"
+ ? "ExpectativasMercadoTop5Anuais"
+ : horizon === "monthly"
+ ? "ExpectativaMercadoMensais"
+ : "ExpectativasMercadoAnuais";
+ const raw = await getJson(`${FOCUS_URL}/${endpoint}`, {
+ $top: top,
+ $format: "json",
+ $orderby: "Data desc",
+ $filter: `Indicador eq '${safe}'`,
+ });
+ return jsonResult(odataValue(raw));
+}
diff --git a/workers/mcp/src/tools/ibge.ts b/workers/mcp/src/tools/ibge.ts
new file mode 100644
index 0000000..cd8d98b
--- /dev/null
+++ b/workers/mcp/src/tools/ibge.ts
@@ -0,0 +1,81 @@
+import { errorResult, getJson, jsonResult } from "../lib/http";
+import ibgeCatalog from "../catalog/ibge.json";
+
+const BASE = "https://servicodados.ibge.gov.br/api/v3/agregados";
+
+type IbgePoint = {
+ periodo: string;
+ valor: number | null;
+ localidade: string;
+ variavel: string;
+ classificacao: string | null;
+};
+
+function parseIbge(raw: unknown): IbgePoint[] {
+ if (!Array.isArray(raw)) {
+ return [];
+ }
+ const results: IbgePoint[] = [];
+ for (const varBlock of raw) {
+ if (!varBlock || typeof varBlock !== "object") continue;
+ const block = varBlock as {
+ variavel?: string;
+ resultados?: Array<{
+ classificacoes?: Array<{ categoria?: Record }>;
+ series?: Array<{
+ localidade?: { nome?: string };
+ serie?: Record;
+ }>;
+ }>;
+ };
+ const variavel = block.variavel ?? "";
+ for (const resultado of block.resultados ?? []) {
+ let classificacao: string | null = null;
+ for (const classif of resultado.classificacoes ?? []) {
+ for (const cat of Object.values(classif.categoria ?? {})) {
+ classificacao = cat;
+ }
+ }
+ for (const serie of resultado.series ?? []) {
+ const localidade = serie.localidade?.nome ?? "Brasil";
+ for (const [periodo, valorStr] of Object.entries(serie.serie ?? {})) {
+ let valor: number | null = null;
+ if (valorStr && valorStr !== "...") {
+ const parsed = Number(valorStr);
+ valor = Number.isNaN(parsed) ? null : parsed;
+ }
+ results.push({ periodo, valor, localidade, variavel, classificacao });
+ }
+ }
+ }
+ }
+ return results;
+}
+
+export async function ibgeIndicator(args: { name?: string; periods?: number }) {
+ if (!args.name) {
+ return jsonResult(ibgeCatalog.indicators);
+ }
+ const info = (ibgeCatalog.indicators as Record)[
+ args.name
+ ];
+ if (!info) {
+ return errorResult(`unknown indicator '${args.name}'`);
+ }
+ const periods = args.periods ?? 12;
+ const raw = await getJson(
+ `${BASE}/${info.agregado}/periodos/-${periods}/variaveis/${info.variavel}`,
+ { localidades: "N1[all]" },
+ );
+ return jsonResult(parseIbge(raw));
+}
+
+export async function ibgeIpcaBreakdown(args: { periods?: number }) {
+ const periods = args.periods ?? 6;
+ const groups = Object.keys(ibgeCatalog.ipca_groups).join(",");
+ const raw = await getJson(`${BASE}/7060/periodos/-${periods}/variaveis/63`, {
+ localidades: "N1[all]",
+ classificacao: `315[${groups}]`,
+ });
+ return jsonResult(parseIbge(raw));
+}
diff --git a/workers/mcp/src/tools/ipea.ts b/workers/mcp/src/tools/ipea.ts
new file mode 100644
index 0000000..ff85f62
--- /dev/null
+++ b/workers/mcp/src/tools/ipea.ts
@@ -0,0 +1,77 @@
+import { errorResult, getJson, jsonResult, odataValue } from "../lib/http";
+import ipeaCatalog from "../catalog/ipea.json";
+
+const BASE = "http://www.ipeadata.gov.br/api/odata4";
+
+function metadataRow(item: Record) {
+ return {
+ sercodigo: item.SERCODIGO,
+ sernome: item.SERNOME,
+ sercomentario: item.SERCOMENTARIO ?? null,
+ serunidade: item.SERUNIDADE ?? null,
+ serperiodicidade: item.SERPERIODICIDADE ?? null,
+ sertema: item.SERTTEMA ?? item.SERTEMA ?? null,
+ serfonte: item.SERFONTE ?? null,
+ };
+}
+
+export async function ipeaSeries(args: {
+ sercodigo?: string;
+ dataset?: "values" | "metadata";
+ top?: number;
+}) {
+ if (!args.sercodigo) {
+ return jsonResult(ipeaCatalog);
+ }
+ if (!/^[A-Za-z0-9_]+$/.test(args.sercodigo)) {
+ return errorResult("invalid SERCODIGO");
+ }
+ if (args.dataset === "metadata") {
+ const raw = await getJson(`${BASE}/Metadados('${args.sercodigo}')`);
+ const items = odataValue(raw) as Record[];
+ if (!items.length) {
+ return errorResult(`unknown SERCODIGO: ${args.sercodigo}`);
+ }
+ return jsonResult(metadataRow(items[0]));
+ }
+ const raw = await getJson(`${BASE}/ValoresSerie(SERCODIGO='${args.sercodigo}')`);
+ const points = (odataValue(raw) as Record[]).map((item) => ({
+ sercodigo: item.SERCODIGO,
+ data: item.VALDATA,
+ valor: item.VALVALOR ?? null,
+ }));
+ const top = args.top ?? 500;
+ return jsonResult(
+ [...points].sort((a, b) => String(b.data).localeCompare(String(a.data))).slice(0, top),
+ );
+}
+
+export async function ipeaSearch(args: { q: string; top?: number }) {
+ const top = args.top ?? 25;
+ const q = args.q.replaceAll("'", "''");
+ const variants = [...new Set([q, q.toLowerCase(), q.toUpperCase(), titleCase(q)])];
+ const parts: string[] = [];
+ for (const variant of variants) {
+ parts.push(`substringof('${variant}', SERNOME)`);
+ parts.push(`substringof('${variant}', SERCODIGO)`);
+ }
+ const raw = await getJson(`${BASE}/Metadados`, {
+ $top: Math.max(top * 2, top),
+ $filter: parts.join(" or "),
+ });
+ const seen = new Set();
+ const out: ReturnType[] = [];
+ for (const item of odataValue(raw) as Record[]) {
+ const row = metadataRow(item);
+ const code = String(row.sercodigo ?? "");
+ if (!code || seen.has(code)) continue;
+ seen.add(code);
+ out.push(row);
+ if (out.length >= top) break;
+ }
+ return jsonResult(out);
+}
+
+function titleCase(value: string): string {
+ return value.replace(/\w\S*/g, (word) => word[0].toUpperCase() + word.slice(1).toLowerCase());
+}
diff --git a/workers/mcp/src/tools/openfinance.ts b/workers/mcp/src/tools/openfinance.ts
new file mode 100644
index 0000000..a859ebc
--- /dev/null
+++ b/workers/mcp/src/tools/openfinance.ts
@@ -0,0 +1,118 @@
+import { getJson, jsonResult } from "../lib/http";
+
+const DATA = "https://data.directory.openbankingbrasil.org.br";
+
+type Participant = Record;
+
+function asDicts(value: unknown): Participant[] {
+ return Array.isArray(value) ? value.filter((item) => item && typeof item === "object") : [];
+}
+
+function summarise(item: Participant) {
+ const servers = asDicts(item.AuthorisationServers);
+ const roles = asDicts(item.OrgDomainRoleClaims)
+ .map((claim) => String(claim.Role ?? ""))
+ .filter(Boolean);
+ let apiResources = 0;
+ for (const server of servers) {
+ apiResources += asDicts(server.ApiResources).length;
+ }
+ return {
+ organisation_id: item.OrganisationId,
+ organisation_name: item.OrganisationName ?? null,
+ registered_name: item.RegisteredName ?? null,
+ registration_number: item.RegistrationNumber ?? null,
+ status: item.Status ?? null,
+ roles: [...new Set(roles)].sort(),
+ authorization_servers: servers.length,
+ api_resources: apiResources,
+ };
+}
+
+export async function openfinanceDirectory(args: {
+ dataset?: "participants" | "endpoints" | "resources" | "roles";
+ role?: string;
+ status?: string;
+ api_family?: string;
+ q?: string;
+ limit?: number;
+}) {
+ const dataset = args.dataset ?? "participants";
+ const limit = args.limit ?? 100;
+ if (dataset === "resources") {
+ return jsonResult([
+ { name: "participants", url: `${DATA}/participants` },
+ { name: "roles", url: `${DATA}/roles` },
+ ]);
+ }
+ if (dataset === "roles") {
+ const raw = await getJson(`${DATA}/roles`, undefined, {
+ maxBytes: 8_000_000,
+ timeoutMs: 30_000,
+ });
+ return jsonResult(asDicts(raw).slice(0, limit));
+ }
+ const raw = asDicts(
+ await getJson(`${DATA}/participants`, undefined, { maxBytes: 8_000_000, timeoutMs: 30_000 }),
+ );
+ const status = args.status ?? "Active";
+ const q = args.q?.toLowerCase();
+ const role = args.role?.toLowerCase();
+ const family = args.api_family?.toLowerCase();
+ const filtered = raw.filter((item) => {
+ if (status && String(item.Status ?? "").toLowerCase() !== status.toLowerCase()) {
+ return false;
+ }
+ if (q) {
+ const hay = `${item.OrganisationName ?? ""} ${item.RegisteredName ?? ""} ${item.RegistrationNumber ?? ""}`.toLowerCase();
+ if (!hay.includes(q)) return false;
+ }
+ if (role) {
+ const roles = asDicts(item.OrgDomainRoleClaims).map((claim) =>
+ String(claim.Role ?? "").toLowerCase(),
+ );
+ if (!roles.includes(role)) return false;
+ }
+ if (family) {
+ let hit = false;
+ for (const server of asDicts(item.AuthorisationServers)) {
+ for (const resource of asDicts(server.ApiResources)) {
+ if (String(resource.ApiFamilyType ?? "").toLowerCase().includes(family)) {
+ hit = true;
+ }
+ }
+ }
+ if (!hit) return false;
+ }
+ return true;
+ });
+ if (dataset === "endpoints") {
+ const endpoints: Record[] = [];
+ for (const org of filtered) {
+ for (const server of asDicts(org.AuthorisationServers)) {
+ for (const resource of asDicts(server.ApiResources)) {
+ if (
+ family &&
+ !String(resource.ApiFamilyType ?? "").toLowerCase().includes(family)
+ ) {
+ continue;
+ }
+ for (const endpoint of asDicts(resource.ApiDiscoveryEndpoints)) {
+ if (!endpoint.ApiEndpoint) continue;
+ endpoints.push({
+ organisation_id: org.OrganisationId,
+ organisation_name: org.OrganisationName ?? null,
+ api_family_type: resource.ApiFamilyType ?? null,
+ api_endpoint: endpoint.ApiEndpoint,
+ });
+ if (endpoints.length >= limit) {
+ return jsonResult(endpoints);
+ }
+ }
+ }
+ }
+ }
+ return jsonResult(endpoints);
+ }
+ return jsonResult(filtered.slice(0, limit).map(summarise));
+}
diff --git a/workers/mcp/src/tools/tesouro.ts b/workers/mcp/src/tools/tesouro.ts
new file mode 100644
index 0000000..f7df20d
--- /dev/null
+++ b/workers/mcp/src/tools/tesouro.ts
@@ -0,0 +1,85 @@
+import { errorResult, getJson, jsonResult } from "../lib/http";
+
+const SICONFI = "https://apidatalake.tesouro.gov.br/ords/siconfi/tt";
+const PAGE = 2000;
+const MAX_PAGES_ENTES = 3;
+const MAX_PAGES_REPORT = 1;
+
+async function paginate(
+ path: string,
+ params: Record,
+ maxPages: number,
+): Promise[]> {
+ const rows: Record[] = [];
+ for (let page = 0; page < maxPages; page += 1) {
+ const raw = (await getJson(`${SICONFI}/${path}`, {
+ ...params,
+ limit: PAGE,
+ offset: page * PAGE,
+ })) as { items?: unknown; hasMore?: boolean };
+ const items = Array.isArray(raw.items) ? raw.items : [];
+ for (const item of items) {
+ if (item && typeof item === "object") {
+ rows.push(item as Record);
+ }
+ }
+ if (!raw.hasMore) break;
+ }
+ return rows;
+}
+
+export async function tesouroSiconfi(args: {
+ report?: "rreo" | "rgf" | "entes";
+ year?: number;
+ period?: number;
+ cod_ibge?: number;
+ poder?: string;
+ anexo?: string;
+}) {
+ const report = args.report ?? "entes";
+ if (report === "entes") {
+ const rows = await paginate("entes", {}, MAX_PAGES_ENTES);
+ return jsonResult(
+ rows.map((row) => ({
+ cod_ibge: row.cod_ibge,
+ uf: row.uf,
+ instituicao: row.instituicao ?? row.ente,
+ esfera: row.esfera,
+ populacao: row.populacao ?? null,
+ })),
+ );
+ }
+ if (args.year === undefined || args.period === undefined || args.cod_ibge === undefined) {
+ return errorResult(`report=${report} requires year, period, and cod_ibge`);
+ }
+ if (report === "rgf") {
+ if (args.period < 1 || args.period > 3) {
+ return errorResult("RGF period is the quadrimestre 1-3");
+ }
+ const rows = await paginate(
+ "rgf",
+ {
+ an_exercicio: args.year,
+ nr_periodo: args.period,
+ co_tipo_demonstrativo: "RGF",
+ co_poder: args.poder ?? "E",
+ id_ente: args.cod_ibge,
+ no_anexo: args.anexo,
+ },
+ MAX_PAGES_REPORT,
+ );
+ return jsonResult(rows.slice(0, 2000));
+ }
+ const rows = await paginate(
+ "rreo",
+ {
+ an_exercicio: args.year,
+ nr_periodo: args.period,
+ co_tipo_demonstrativo: "RREO",
+ id_ente: args.cod_ibge,
+ no_anexo: args.anexo,
+ },
+ MAX_PAGES_REPORT,
+ );
+ return jsonResult(rows.slice(0, 2000));
+}
diff --git a/workers/mcp/tsconfig.json b/workers/mcp/tsconfig.json
new file mode 100644
index 0000000..51190d1
--- /dev/null
+++ b/workers/mcp/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ES2022",
+ "moduleResolution": "Bundler",
+ "lib": ["ES2022"],
+ "strict": true,
+ "skipLibCheck": true,
+ "noEmit": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "types": ["@cloudflare/workers-types"]
+ },
+ "include": ["src/**/*.ts", "worker-configuration.d.ts"]
+}
diff --git a/workers/mcp/worker-configuration.d.ts b/workers/mcp/worker-configuration.d.ts
new file mode 100644
index 0000000..ba0ff99
--- /dev/null
+++ b/workers/mcp/worker-configuration.d.ts
@@ -0,0 +1,3 @@
+interface Env {
+ ASSETS: Fetcher;
+}
diff --git a/workers/mcp/wrangler.toml b/workers/mcp/wrangler.toml
new file mode 100644
index 0000000..29e9c80
--- /dev/null
+++ b/workers/mcp/wrangler.toml
@@ -0,0 +1,22 @@
+name = "openfindata-mcp"
+main = "src/index.ts"
+compatibility_date = "2026-08-13"
+account_id = "d8588d9ee01519d5cc5adeebc7f83d32"
+workers_dev = true
+
+[assets]
+directory = "./public"
+binding = "ASSETS"
+html_handling = "auto-trailing-slash"
+run_worker_first = true
+
+[observability]
+enabled = true
+
+[[routes]]
+pattern = "openfindata.com.br"
+custom_domain = true
+
+[[routes]]
+pattern = "www.openfindata.com.br"
+custom_domain = true
From a220274a75496e77980a154fff7ba5f4518d8597 Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Fri, 21 Aug 2026 16:47:45 +0000
Subject: [PATCH 2/4] feat: add predictable public Worker /mcp rate limits
Enforce 60 req/60s and 20/10s burst per IP on /mcp via Workers Rate Limit
bindings. Overflow is synchronous 429 + Retry-After. Landing and /health
stay unlimited.
---
docs/DEPLOY_WORKERS_MCP.md | 8 +++-
docs/MCP_SURFACE.md | 4 +-
tests/test_workers_mcp_rate_limit.py | 64 +++++++++++++++++++++++++++
workers/mcp/public/index.html | 5 +++
workers/mcp/src/index.ts | 9 ++++
workers/mcp/src/rateLimit.ts | 53 ++++++++++++++++++++++
workers/mcp/worker-configuration.d.ts | 2 +
workers/mcp/wrangler.toml | 18 ++++++++
8 files changed, 160 insertions(+), 3 deletions(-)
create mode 100644 tests/test_workers_mcp_rate_limit.py
create mode 100644 workers/mcp/src/rateLimit.ts
diff --git a/docs/DEPLOY_WORKERS_MCP.md b/docs/DEPLOY_WORKERS_MCP.md
index 84d9543..8cab3a1 100644
--- a/docs/DEPLOY_WORKERS_MCP.md
+++ b/docs/DEPLOY_WORKERS_MCP.md
@@ -47,8 +47,12 @@ curl -sS https://openfindata.com.br/health
Upstream calls no Worker têm timeout (15s) e teto de payload (2 MB;
8 MB só no Directory Open Finance). Séries BCB sem intervalo caem em
-`last_n≤200`. Coloque um rate limit no hostname no dashboard Cloudflare
-(WAF / Rate limiting rules) — o Worker não tem SlowAPI.
+`last_n≤200`.
+
+`/mcp` usa Workers Rate Limit bindings (não Cloudflare Queues): 60 req /
+60s por IP e pico 20 / 10s. Overflow é síncrono: HTTP 429 + `Retry-After`
+e corpo `{ "error": "rate_limited" }`. Landing `/` e `/health` ficam
+fora do limite. Os contadores são por localização Cloudflare.
## FastAPI interno (Tailscale)
diff --git a/docs/MCP_SURFACE.md b/docs/MCP_SURFACE.md
index 79c15cf..4fc883b 100644
--- a/docs/MCP_SURFACE.md
+++ b/docs/MCP_SURFACE.md
@@ -3,7 +3,9 @@
> Status: implemented (alpha curated catalog). REST is untouched.
> Internal MCP (FastAPI): [`src/findata/api/mcp_app.py`](../src/findata/api/mcp_app.py).
> Public MCP (Cloudflare Worker, JSON sources only): [`workers/mcp`](../workers/mcp)
-> and [`docs/DEPLOY_WORKERS_MCP.md`](DEPLOY_WORKERS_MCP.md).
+> and [`docs/DEPLOY_WORKERS_MCP.md`](DEPLOY_WORKERS_MCP.md). Public `/mcp` is
+> 60 req/60s per IP with a 20/10s burst; overflow is 429 + Retry-After (no queue,
+> no code mode, no API key).
## Problem
diff --git a/tests/test_workers_mcp_rate_limit.py b/tests/test_workers_mcp_rate_limit.py
new file mode 100644
index 0000000..9c6ef6a
--- /dev/null
+++ b/tests/test_workers_mcp_rate_limit.py
@@ -0,0 +1,64 @@
+"""Contract checks for the public Worker /mcp rate limits.
+
+The Worker is TypeScript; this file only locks the agreed numbers and the
+429 shape so CI notices drift without a Wrangler test harness.
+"""
+
+from __future__ import annotations
+
+import tomllib
+from pathlib import Path
+
+REPO = Path(__file__).resolve().parents[1]
+WORKER = REPO / "workers" / "mcp"
+
+
+def _wrangler() -> dict[str, object]:
+ return tomllib.loads((WORKER / "wrangler.toml").read_text(encoding="utf-8"))
+
+
+def _ratelimits() -> dict[str, dict[str, object]]:
+ raw = _wrangler()["ratelimits"]
+ assert isinstance(raw, list)
+ named: dict[str, dict[str, object]] = {}
+ for item in raw:
+ assert isinstance(item, dict)
+ name = item["name"]
+ assert isinstance(name, str)
+ named[name] = item
+ return named
+
+
+def test_wrangler_binds_minute_and_burst_limits() -> None:
+ limits = _ratelimits()
+ assert limits["MCP_RATE_LIMIT"]["simple"] == {"limit": 60, "period": 60}
+ assert limits["MCP_BURST_LIMIT"]["simple"] == {"limit": 20, "period": 10}
+ assert limits["MCP_RATE_LIMIT"]["namespace_id"] != limits["MCP_BURST_LIMIT"]["namespace_id"]
+
+
+def test_handler_enforces_limits_only_on_mcp() -> None:
+ handler = (WORKER / "src" / "index.ts").read_text(encoding="utf-8")
+ health_idx = handler.index('url.pathname === "/health"')
+ mcp_idx = handler.index('url.pathname === "/mcp"')
+ limit_idx = handler.index("await enforceMcpRateLimits")
+ assert health_idx < mcp_idx < limit_idx
+ health_block = handler[health_idx:mcp_idx]
+ assert "enforceMcpRateLimits" not in health_block
+
+
+def test_rate_limit_response_is_sync_429() -> None:
+ source = (WORKER / "src" / "rateLimit.ts").read_text(encoding="utf-8")
+ assert "error: RATE_LIMITED_ERROR" in source or 'error: "rate_limited"' in source
+ assert "rate_limited" in source
+ assert "retry-after" in source
+ assert "status: 429" in source
+ assert "Queues" not in source
+
+
+def test_landing_states_fair_use() -> None:
+ html = (WORKER / "public" / "index.html").read_text(encoding="utf-8")
+ assert "60 req/min" in html
+ assert "API key" in html
+ assert "execução de código" in html
+ assert "429" in html
+ assert "Retry-After" in html
diff --git a/workers/mcp/public/index.html b/workers/mcp/public/index.html
index 4e6f193..883c967 100644
--- a/workers/mcp/public/index.html
+++ b/workers/mcp/public/index.html
@@ -28,6 +28,11 @@ MCP público no Cloudflare Workers
Superfície pública: /mcp (Streamable HTTP).
REST/FastAPI, CLI e code mode não ficam na internet — só na rede Tailscale.
+
+ Uso justo: cerca de 60 req/min por IP (pico 20 / 10s). Sem API key.
+ Sem execução de código no endpoint público. Excesso em
+ /mcp responde 429 + Retry-After.
+
Tools neste Worker (fontes JSON públicas, sem processar Python):
bcb_series bcb_ptax bcb_focus
diff --git a/workers/mcp/src/index.ts b/workers/mcp/src/index.ts
index be27f80..deb6072 100644
--- a/workers/mcp/src/index.ts
+++ b/workers/mcp/src/index.ts
@@ -1,4 +1,5 @@
import { createMcpHandler } from "agents/mcp/server";
+import { enforceMcpRateLimits } from "./rateLimit";
import { createServer } from "./server";
const mcp = createMcpHandler(createServer, { route: "/mcp" });
@@ -15,6 +16,14 @@ export default {
});
}
if (url.pathname === "/mcp" || url.pathname.startsWith("/mcp/")) {
+ const limited = await enforceMcpRateLimits(
+ request,
+ env.MCP_RATE_LIMIT,
+ env.MCP_BURST_LIMIT,
+ );
+ if (limited) {
+ return limited;
+ }
return mcp(request, env, ctx);
}
return env.ASSETS.fetch(request);
diff --git a/workers/mcp/src/rateLimit.ts b/workers/mcp/src/rateLimit.ts
new file mode 100644
index 0000000..c66c62e
--- /dev/null
+++ b/workers/mcp/src/rateLimit.ts
@@ -0,0 +1,53 @@
+export const MCP_MINUTE_LIMIT = 60;
+export const MCP_MINUTE_PERIOD_S = 60;
+export const MCP_BURST_LIMIT = 20;
+export const MCP_BURST_PERIOD_S = 10;
+export const RATE_LIMITED_ERROR = "rate_limited";
+
+export type LimitBinding = {
+ limit(options: { key: string }): Promise<{ success: boolean }>;
+};
+
+export function clientKey(request: Request): string {
+ const cfIp = request.headers.get("cf-connecting-ip")?.trim();
+ if (cfIp) {
+ return `mcp:${cfIp}`;
+ }
+ const forwarded = request.headers.get("x-forwarded-for");
+ if (forwarded) {
+ const first = forwarded.split(",")[0]?.trim();
+ if (first) {
+ return `mcp:${first}`;
+ }
+ }
+ return "mcp:unknown";
+}
+
+export function rateLimitedResponse(retryAfterSeconds: number): Response {
+ return new Response(JSON.stringify({ error: RATE_LIMITED_ERROR }), {
+ status: 429,
+ headers: {
+ "content-type": "application/json; charset=utf-8",
+ "retry-after": String(retryAfterSeconds),
+ },
+ });
+}
+
+export async function enforceMcpRateLimits(
+ request: Request,
+ minute: LimitBinding,
+ burst: LimitBinding,
+): Promise {
+ const key = clientKey(request);
+ const [minuteResult, burstResult] = await Promise.all([
+ minute.limit({ key }),
+ burst.limit({ key }),
+ ]);
+ if (!minuteResult.success) {
+ return rateLimitedResponse(MCP_MINUTE_PERIOD_S);
+ }
+ if (!burstResult.success) {
+ return rateLimitedResponse(MCP_BURST_PERIOD_S);
+ }
+ return null;
+}
diff --git a/workers/mcp/worker-configuration.d.ts b/workers/mcp/worker-configuration.d.ts
index ba0ff99..da59afb 100644
--- a/workers/mcp/worker-configuration.d.ts
+++ b/workers/mcp/worker-configuration.d.ts
@@ -1,3 +1,5 @@
interface Env {
ASSETS: Fetcher;
+ MCP_RATE_LIMIT: RateLimit;
+ MCP_BURST_LIMIT: RateLimit;
}
diff --git a/workers/mcp/wrangler.toml b/workers/mcp/wrangler.toml
index 29e9c80..03e75a0 100644
--- a/workers/mcp/wrangler.toml
+++ b/workers/mcp/wrangler.toml
@@ -13,6 +13,24 @@ run_worker_first = true
[observability]
enabled = true
+# Public /mcp only. Period must be 10 or 60 (Workers Rate Limit API).
+# Counters are per Cloudflare location, keyed by client IP in the handler.
+[[ratelimits]]
+name = "MCP_RATE_LIMIT"
+namespace_id = "45801"
+
+ [ratelimits.simple]
+ limit = 60
+ period = 60
+
+[[ratelimits]]
+name = "MCP_BURST_LIMIT"
+namespace_id = "45802"
+
+ [ratelimits.simple]
+ limit = 20
+ period = 10
+
[[routes]]
pattern = "openfindata.com.br"
custom_domain = true
From 7e51654a0c337953f86ddb11055e00abbf3423dc Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Fri, 21 Aug 2026 16:57:12 +0000
Subject: [PATCH 3/4] test: execute Worker rate-limit helpers in node:test
Cover 429/Retry-After and IP keying with fake Rate Limit bindings.
Stop trusting X-Forwarded-For when CF-Connecting-IP is absent.
---
tests/test_workers_mcp_rate_limit.py | 21 ++++++++++
workers/mcp/src/rateLimit.test.ts | 63 ++++++++++++++++++++++++++++
workers/mcp/src/rateLimit.ts | 14 ++-----
3 files changed, 87 insertions(+), 11 deletions(-)
create mode 100644 workers/mcp/src/rateLimit.test.ts
diff --git a/tests/test_workers_mcp_rate_limit.py b/tests/test_workers_mcp_rate_limit.py
index 9c6ef6a..616c242 100644
--- a/tests/test_workers_mcp_rate_limit.py
+++ b/tests/test_workers_mcp_rate_limit.py
@@ -6,9 +6,13 @@
from __future__ import annotations
+import shutil
+import subprocess
import tomllib
from pathlib import Path
+import pytest
+
REPO = Path(__file__).resolve().parents[1]
WORKER = REPO / "workers" / "mcp"
@@ -62,3 +66,20 @@ def test_landing_states_fair_use() -> None:
assert "execução de código" in html
assert "429" in html
assert "Retry-After" in html
+
+
+@pytest.mark.skipif(shutil.which("node") is None, reason="node not installed")
+def test_rate_limit_helpers_execute_in_node() -> None:
+ result = subprocess.run(
+ [
+ "node",
+ "--experimental-strip-types",
+ "--test",
+ str(WORKER / "src" / "rateLimit.test.ts"),
+ ],
+ cwd=WORKER,
+ check=False,
+ capture_output=True,
+ text=True,
+ )
+ assert result.returncode == 0, result.stdout + result.stderr
diff --git a/workers/mcp/src/rateLimit.test.ts b/workers/mcp/src/rateLimit.test.ts
new file mode 100644
index 0000000..3b0effa
--- /dev/null
+++ b/workers/mcp/src/rateLimit.test.ts
@@ -0,0 +1,63 @@
+import assert from "node:assert/strict";
+import { test } from "node:test";
+import {
+ MCP_BURST_PERIOD_S,
+ MCP_MINUTE_PERIOD_S,
+ clientKey,
+ enforceMcpRateLimits,
+ rateLimitedResponse,
+} from "./rateLimit.ts";
+
+function requestWith(headers: Record): Request {
+ return new Request("https://openfindata.com.br/mcp", { headers });
+}
+
+test("clientKey prefers cf-connecting-ip", () => {
+ const request = requestWith({
+ "cf-connecting-ip": "198.51.100.7",
+ "x-forwarded-for": "203.0.113.9",
+ });
+ assert.equal(clientKey(request), "mcp:198.51.100.7");
+});
+
+test("clientKey does not trust x-forwarded-for", () => {
+ assert.equal(clientKey(requestWith({ "x-forwarded-for": "203.0.113.9" })), "mcp:unknown");
+});
+
+test("429 body and Retry-After are synchronous", async () => {
+ const response = rateLimitedResponse(MCP_MINUTE_PERIOD_S);
+ assert.equal(response.status, 429);
+ assert.equal(response.headers.get("retry-after"), String(MCP_MINUTE_PERIOD_S));
+ assert.deepEqual(await response.json(), { error: "rate_limited" });
+});
+
+test("minute limit failure returns Retry-After 60", async () => {
+ const response = await enforceMcpRateLimits(
+ requestWith({ "cf-connecting-ip": "198.51.100.7" }),
+ { limit: async () => ({ success: false }) },
+ { limit: async () => ({ success: true }) },
+ );
+ assert.ok(response);
+ assert.equal(response.status, 429);
+ assert.equal(response.headers.get("retry-after"), String(MCP_MINUTE_PERIOD_S));
+});
+
+test("burst limit failure returns Retry-After 10", async () => {
+ const response = await enforceMcpRateLimits(
+ requestWith({ "cf-connecting-ip": "198.51.100.7" }),
+ { limit: async () => ({ success: true }) },
+ { limit: async () => ({ success: false }) },
+ );
+ assert.ok(response);
+ assert.equal(response.status, 429);
+ assert.equal(response.headers.get("retry-after"), String(MCP_BURST_PERIOD_S));
+});
+
+test("both limits succeeding returns null", async () => {
+ const response = await enforceMcpRateLimits(
+ requestWith({ "cf-connecting-ip": "198.51.100.7" }),
+ { limit: async () => ({ success: true }) },
+ { limit: async () => ({ success: true }) },
+ );
+ assert.equal(response, null);
+});
diff --git a/workers/mcp/src/rateLimit.ts b/workers/mcp/src/rateLimit.ts
index c66c62e..f21170f 100644
--- a/workers/mcp/src/rateLimit.ts
+++ b/workers/mcp/src/rateLimit.ts
@@ -9,18 +9,10 @@ export type LimitBinding = {
};
export function clientKey(request: Request): string {
+ // Production Workers always set CF-Connecting-IP. Do not fall back to
+ // X-Forwarded-For: that header is client-spoofable when the CF header is absent.
const cfIp = request.headers.get("cf-connecting-ip")?.trim();
- if (cfIp) {
- return `mcp:${cfIp}`;
- }
- const forwarded = request.headers.get("x-forwarded-for");
- if (forwarded) {
- const first = forwarded.split(",")[0]?.trim();
- if (first) {
- return `mcp:${first}`;
- }
- }
- return "mcp:unknown";
+ return cfIp ? `mcp:${cfIp}` : "mcp:unknown";
}
export function rateLimitedResponse(retryAfterSeconds: number): Response {
From 1357d81bafb4a30330edc9298db61ec59132c4a0 Mon Sep 17 00:00:00 2001
From: Cursor Agent
Date: Fri, 21 Aug 2026 16:57:19 +0000
Subject: [PATCH 4/4] chore: keep Worker node:test files out of tsc include
---
workers/mcp/tsconfig.json | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/workers/mcp/tsconfig.json b/workers/mcp/tsconfig.json
index 51190d1..f6c01a5 100644
--- a/workers/mcp/tsconfig.json
+++ b/workers/mcp/tsconfig.json
@@ -11,5 +11,6 @@
"isolatedModules": true,
"types": ["@cloudflare/workers-types"]
},
- "include": ["src/**/*.ts", "worker-configuration.d.ts"]
+ "include": ["src/**/*.ts", "worker-configuration.d.ts"],
+ "exclude": ["src/**/*.test.ts"]
}