This is an automated email from the ASF dual-hosted git repository.
hanahmily pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
The following commit(s) were added to refs/heads/main by this push:
new 0e3254c99 Add the codex/claude plugin to bydbQL (#1201)
0e3254c99 is described below
commit 0e3254c997068e589ad64b3723a67dc884d62aa7
Author: Jingyi Qu <[email protected]>
AuthorDate: Tue Jul 7 11:50:51 2026 +0800
Add the codex/claude plugin to bydbQL (#1201)
* feat: add BanyanDB nl2ql plugin
---
.agents/plugins/marketplace.json | 20 +
.claude-plugin/marketplace.json | 13 +
.claude-plugin/plugin.json | 37 +
.codex-plugin/plugin.json | 37 +
.github/workflows/ci.yml | 2 +
.mcp.json | 15 +
CHANGES.md | 1 +
docs/menu.yml | 2 +
docs/operation/mcp/build.md | 16 +
docs/operation/mcp/plugin.md | 123 +++
mcp/.gitignore | 1 +
mcp/.licenserc.yaml | 2 +
mcp/Makefile | 23 +-
mcp/package-lock.json | 1443 ++++++++++++++++++++++++++-
mcp/package.json | 8 +-
mcp/src/index.ts | 2 +-
mcp/src/query/parse-validator.test.ts | 79 ++
mcp/src/query/parse-validator.ts | 123 +++
mcp/src/query/validation.test.ts | 62 ++
mcp/src/query/validation.ts | 2 +-
mcp/src/server/mcp.ts | 48 +
mcp/tools/build-validator.js | 44 +
mcp/tools/bydbql-parse/main.go | 96 ++
mcp/tools/bydbql-parse/main_test.go | 114 +++
mcp/tools/bydbql-parse/testdata/invalid.txt | 22 +
mcp/tools/bydbql-parse/testdata/valid.txt | 28 +
mcp/tools/check-plugin-manifests.js | 88 ++
mcp/tsconfig.json | 2 +-
skills/bydbql/SKILL.md | 39 +
skills/bydbql/references/examples.md | 30 +
skills/bydbql/references/safety.md | 18 +
skills/bydbql/references/syntax.md | 374 +++++++
32 files changed, 2879 insertions(+), 35 deletions(-)
diff --git a/.agents/plugins/marketplace.json b/.agents/plugins/marketplace.json
new file mode 100644
index 000000000..7484f3fba
--- /dev/null
+++ b/.agents/plugins/marketplace.json
@@ -0,0 +1,20 @@
+{
+ "name": "banyandb",
+ "interface": {
+ "displayName": "BanyanDB"
+ },
+ "plugins": [
+ {
+ "name": "banyandb-bydbql",
+ "source": {
+ "source": "local",
+ "path": "./"
+ },
+ "policy": {
+ "installation": "AVAILABLE",
+ "authentication": "ON_INSTALL"
+ },
+ "category": "Developer Tools"
+ }
+ ]
+}
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
new file mode 100644
index 000000000..f2d608f80
--- /dev/null
+++ b/.claude-plugin/marketplace.json
@@ -0,0 +1,13 @@
+{
+ "name": "banyandb",
+ "owner": {
+ "name": "Apache SkyWalking BanyanDB contributors"
+ },
+ "plugins": [
+ {
+ "name": "banyandb-bydbql",
+ "source": "./",
+ "description": "Generate, validate, and execute read-only BanyanDB
BydbQL for SkyWalking data."
+ }
+ ]
+}
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
new file mode 100644
index 000000000..bb6b66548
--- /dev/null
+++ b/.claude-plugin/plugin.json
@@ -0,0 +1,37 @@
+{
+ "name": "banyandb-bydbql",
+ "version": "0.1.0",
+ "description": "Generate, validate, and execute read-only BanyanDB BydbQL
for SkyWalking data.",
+ "author": {
+ "name": "Apache SkyWalking BanyanDB contributors"
+ },
+ "homepage": "https://github.com/apache/skywalking-banyandb",
+ "repository": "https://github.com/apache/skywalking-banyandb",
+ "license": "Apache-2.0",
+ "keywords": [
+ "banyandb",
+ "bydbql",
+ "mcp",
+ "observability",
+ "query"
+ ],
+ "skills": "./skills/",
+ "mcpServers": "./.mcp.json",
+ "interface": {
+ "displayName": "BanyanDB BydbQL",
+ "shortDescription": "Generate and run BanyanDB BydbQL.",
+ "longDescription": "Translate natural language into read-only BanyanDB
BydbQL for SkyWalking metrics, logs, traces, and properties with live schema
discovery and syntax validation.",
+ "developerName": "Apache SkyWalking BanyanDB contributors",
+ "category": "Developer Tools",
+ "capabilities": [
+ "Interactive",
+ "Read"
+ ],
+ "websiteURL": "https://github.com/apache/skywalking-banyandb",
+ "defaultPrompt": [
+ "Generate a BydbQL query for recent BanyanDB metrics.",
+ "Query recent SkyWalking trace records with BydbQL."
+ ],
+ "brandColor": "#2F72BC"
+ }
+}
diff --git a/.codex-plugin/plugin.json b/.codex-plugin/plugin.json
new file mode 100644
index 000000000..35d168ac8
--- /dev/null
+++ b/.codex-plugin/plugin.json
@@ -0,0 +1,37 @@
+{
+ "name": "banyandb-bydbql",
+ "version": "0.1.0+codex.20260701120139",
+ "description": "Generate, validate, and execute read-only BanyanDB BydbQL
for SkyWalking data.",
+ "author": {
+ "name": "Apache SkyWalking BanyanDB contributors"
+ },
+ "homepage": "https://github.com/apache/skywalking-banyandb",
+ "repository": "https://github.com/apache/skywalking-banyandb",
+ "license": "Apache-2.0",
+ "keywords": [
+ "banyandb",
+ "bydbql",
+ "mcp",
+ "observability",
+ "query"
+ ],
+ "skills": "./skills/",
+ "mcpServers": "./.mcp.json",
+ "interface": {
+ "displayName": "BanyanDB BydbQL",
+ "shortDescription": "Generate and run BanyanDB BydbQL.",
+ "longDescription": "Translate natural language into read-only BanyanDB
BydbQL for SkyWalking metrics, logs, traces, and properties with live schema
discovery and syntax validation.",
+ "developerName": "Apache SkyWalking BanyanDB contributors",
+ "category": "Developer Tools",
+ "capabilities": [
+ "Interactive",
+ "Read"
+ ],
+ "websiteURL": "https://github.com/apache/skywalking-banyandb",
+ "defaultPrompt": [
+ "Generate a BydbQL query for recent BanyanDB metrics.",
+ "Query recent SkyWalking trace records with BydbQL."
+ ],
+ "brandColor": "#2F72BC"
+ }
+}
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5d4edad24..6c9c14598 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -81,6 +81,8 @@ jobs:
run: make check-req
- name: Build binaries, UI dist and MCP dist
run: make build
+ - name: Test MCP (Go validator tool + TypeScript safety layer)
+ run: make -C mcp test
- name: Lint
run: make lint
- name: Generate dependencies licenses
diff --git a/.mcp.json b/.mcp.json
new file mode 100644
index 000000000..93c3c34a2
--- /dev/null
+++ b/.mcp.json
@@ -0,0 +1,15 @@
+{
+ "mcpServers": {
+ "banyandb": {
+ "cwd": ".",
+ "command": "node",
+ "args": [
+ "./mcp/dist/index.js"
+ ],
+ "env": {
+ "TRANSPORT": "stdio",
+ "BANYANDB_ADDRESS": "localhost:17900"
+ }
+ }
+ }
+}
diff --git a/CHANGES.md b/CHANGES.md
index bcaee768d..0884bae7e 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -74,6 +74,7 @@ Release Notes.
- Add the storage-node in-merge trace retention filter
(`PIPELINE_EVENT_MERGE`): a per-group sampler chain evaluates traces at merge
time and drops non-retained traces from both the core part and the coupled sidx
(`sidx.Merge` gains a keep predicate; a nil predicate restores the wholesale
merge). Chain execution fails open on panic/error/length-mismatch/timeout, with
a consecutive-timeout circuit breaker. Samplers are native Go plugins (`.so`)
loaded at startup from `--trace-pipeline-con [...]
- Register/update/remove in-merge trace retention samplers at runtime without
restarting the data node, replacing the static `--trace-pipeline-config` boot
loader (now removed). The sampler pipeline config is carried on
`common.v1.Group` (new optional `pipeline` field, with
`TracePipelineConfig`/`SamplerPlugin` relocated from `pipeline/v1` into the
leaf `common/v1` to avoid a `common`→`pipeline` import cycle) and delivered
through the existing `KindGroup` schema watch; the data node rebu [...]
- Add FODC memory-pressure pprof capture: agent grabs heap/goroutine profiles
when RSS nears the cgroup limit, served via the proxy.
+- Add a Claude/Codex plugin packaging the BanyanDB MCP server with a
parse-only `validate_bydbql` tool (backed by the `mcp/tools/bydbql-parse` Go
validator) and a BydbQL skill for read-only natural-language-to-BydbQL
generation over STREAM/MEASURE/TRACE/PROPERTY resources.
### Bug Fixes
diff --git a/docs/menu.yml b/docs/menu.yml
index f2821ad6e..4acefec3b 100644
--- a/docs/menu.yml
+++ b/docs/menu.yml
@@ -177,6 +177,8 @@ catalog:
path: "/operation/mcp/inspector"
- name: "Build and Package"
path: "/operation/mcp/build"
+ - name: "Plugin Packaging"
+ path: "/operation/mcp/plugin"
- name: "First Occurrence Data Collection (FODC)"
catalog:
- name: "Overview"
diff --git a/docs/operation/mcp/build.md b/docs/operation/mcp/build.md
index 45f15ba10..a1d5bd59b 100644
--- a/docs/operation/mcp/build.md
+++ b/docs/operation/mcp/build.md
@@ -62,6 +62,22 @@ npm run build
This compiles TypeScript to JavaScript in the `dist/` directory.
+### 2b. Build the BydbQL validator binary
+
+The `validate_bydbql` tool runs the prebuilt Go validator at
`tools/bin/bydbql-parse`.
+Build it (requires the Go toolchain) so the tool does not depend on `go run` at
+runtime:
+
+```bash
+npm run build:validator
+```
+
+To produce both the TypeScript output and the validator binary in one step:
+
+```bash
+npm run build:all
+```
+
The runtime is organized into small modules:
- `src/index.ts` wires together configuration, transport selection, and
startup.
diff --git a/docs/operation/mcp/plugin.md b/docs/operation/mcp/plugin.md
new file mode 100644
index 000000000..b283ec743
--- /dev/null
+++ b/docs/operation/mcp/plugin.md
@@ -0,0 +1,123 @@
+# BydbQL Plugin Packaging
+
+The BanyanDB MCP server is also packaged as a Claude/Codex plugin so a host
model
+can generate, validate, and run read-only BydbQL. This page describes the
plugin
+layout, the build it requires before it can load, and how the manifests are
kept
+in sync.
+
+## Layout
+
+| Path | Purpose |
+|------|---------|
+| `.claude-plugin/plugin.json` | Claude Code plugin manifest. |
+| `.claude-plugin/marketplace.json` | Claude Code plugin marketplace catalog. |
+| `.codex-plugin/plugin.json` | Codex plugin manifest. |
+| `.agents/plugins/marketplace.json` | Codex plugin marketplace catalog. |
+| `.mcp.json` | MCP server definition referenced by both manifests. |
+| `skills/bydbql/` | The `bydbql` skill and its `references/`. |
+| `mcp/` | The MCP server source, the TypeScript build output (`dist/`), and
the `bydbql-parse` validator tool. |
+
+## Install from a GitHub marketplace
+
+The repository includes both a Codex marketplace at
`.agents/plugins/marketplace.json`
+and a Claude Code marketplace at `.claude-plugin/marketplace.json`.
+Both can install the plugin directly from GitHub; users do not need to
+clone the repository just to make the plugin appear in the plugin directory.
+
+### Claude Code
+
+```bash
+# Direct install (no marketplace needed):
+/plugin install apache/skywalking-banyandb
+
+# Or add the marketplace first, then install from it:
+/plugin marketplace add apache/skywalking-banyandb
+/plugin install banyandb-bydbql@banyandb
+```
+
+### Codex
+
+```bash
+codex plugin marketplace add apache/skywalking-banyandb --ref main
+codex plugin add banyandb-bydbql@banyandb
+```
+
+For a fork or development branch, replace the repository and ref:
+
+```bash
+# Claude Code
+/plugin marketplace add JophieQu/skywalking-banyandb --ref <branch>
+/plugin install banyandb-bydbql@banyandb
+
+# Codex
+codex plugin marketplace add JophieQu/skywalking-banyandb --ref <branch>
+codex plugin add banyandb-bydbql@banyandb
+```
+
+Both marketplace entries point at `./` because the plugin manifest, MCP
+configuration, skills, and MCP source live at the repository root.
+
+This remote install flow publishes the plugin to Claude Code and Codex. The
+current MCP runtime is source-based, so starting the MCP tools still requires
+the build artifacts described below to exist in the installed plugin copy.
+
+## Required build before loading
+
+Both the MCP server (`mcp/dist/index.js`) and the BydbQL parse validator
+(`mcp/tools/bin/bydbql-parse`) are build artifacts. `dist/` and `tools/bin/`
are
+git-ignored, so a fresh checkout has neither. Build both before loading the
+plugin or starting the server:
+
+```bash
+cd mcp
+npm install
+npm run build:all # equivalent to: npm run build && npm run build:validator
+```
+
+- `npm run build` compiles TypeScript into `mcp/dist/`.
+- `npm run build:validator` compiles the Go validator into
`mcp/tools/bin/bydbql-parse` (requires the Go toolchain).
+
+`validate_bydbql` invokes the **prebuilt** `bydbql-parse` binary. It
intentionally
+does not fall back to `go run` at query time, so the plugin's runtime does not
+depend on a Go toolchain and does not pay a cold-compile cost inside the
+validation timeout. If the binary is missing, `validate_bydbql` returns an
+actionable error asking you to run `npm run build:validator` rather than
silently
+recompiling.
+
+## `.mcp.json` working directory
+
+```json
+{
+ "mcpServers": {
+ "banyandb": {
+ "cwd": ".",
+ "command": "node",
+ "args": ["./mcp/dist/index.js"],
+ "env": { "TRANSPORT": "stdio", "BANYANDB_ADDRESS": "localhost:17900" }
+ }
+ }
+}
+```
+
+Both Claude Code and Codex resolve the plugin root to the repository root
+(whether installed via marketplace or direct GitHub install). The MCP loader
+resolves `cwd: "."` relative to that installed plugin root, then resolves
+`./mcp/dist/index.js` from the same directory. This means the checked-out
layout
+and installed layout are expected to match: `.mcp.json`, `mcp/dist/`,
+`mcp/tools/bin/`, and `skills/` all live under the plugin root. If you manually
+copy only part of the repository, keep that layout or update the relative paths
+in `.mcp.json`.
+
+## Keeping the manifests in sync
+
+`.claude-plugin/plugin.json` and `.codex-plugin/plugin.json` are intentionally
+identical except for the `version` field — the Codex manifest carries a
+`+codex.<timestamp>` build-metadata suffix. `make -C mcp test` runs
+`npm run check:plugin-manifests`, which fails if any non-version field drifts
or
+if the Codex version does not use the Claude version as its base. When you
change
+one manifest (description, keywords, `interface`, `skills`, `mcpServers`,
etc.),
+mirror the exact change into the other and run that check.
+
+The marketplace catalogs (`.claude-plugin/marketplace.json` and
+`.agents/plugins/marketplace.json`) serve the same plugin under their
respective
+platform formats and do not need to be field-identical.
diff --git a/mcp/.gitignore b/mcp/.gitignore
index 37188959e..4e9fb880b 100644
--- a/mcp/.gitignore
+++ b/mcp/.gitignore
@@ -17,6 +17,7 @@
node_modules/
dist/
+tools/bin/
*.log
.DS_Store
.env
diff --git a/mcp/.licenserc.yaml b/mcp/.licenserc.yaml
index 1d01566ca..72e394464 100644
--- a/mcp/.licenserc.yaml
+++ b/mcp/.licenserc.yaml
@@ -66,6 +66,8 @@ header: # `header` section is configurations for source codes
license header.
- '*.json'
- 'LICENSE*'
- '*.md'
+ - '**/testdata/**'
+ - 'tools/bin'
- '.prettierignore'
- '.dockerignore'
- 'banyandb-data'
diff --git a/mcp/Makefile b/mcp/Makefile
index fb6e87ef8..e7db48c01 100644
--- a/mcp/Makefile
+++ b/mcp/Makefile
@@ -34,7 +34,7 @@ check-version: install
.PHONY: install
install:
- npm install
+ npm ci
.PHONY: lint
lint: install
@@ -70,16 +70,19 @@ clean-build:
rm -rf dist
.PHONY: test
-test:
- @echo "No test"
+test: install
+ npm run check:plugin-manifests
+ npm run build:validator
+ npm test
+ go test ./tools/...
.PHONY: test-race
-test-race:
- @echo "No test"
+test-race: install
+ npm test
.PHONY: test-coverage
-test-coverage:
- @echo "No test"
+test-coverage: install
+ go test -cover ./tools/...
.PHONY: release
release: build
@@ -96,8 +99,11 @@ license-fix: $(LICENSE_EYE) ## Fix license header issues
$(LICENSE_EYE) header fix
.PHONY: license-dep
-license-dep:$(LICENSE_EYE)
+license-dep: $(LICENSE_EYE)
@rm -rf licenses
+ @lock_backup=$$(mktemp); \
+ cp package-lock.json "$$lock_backup"; \
+ trap 'cp "$$lock_backup" package-lock.json; rm -f "$$lock_backup"'
EXIT; \
$(LICENSE_EYE) dep resolve -o mcp-licenses -s LICENSE.tpl
.PHONY: vendor-update
@@ -112,4 +118,3 @@ check:
$(MAKE) test-coverage
$(MAKE) license-dep
$(MAKE) vendor-update
-
diff --git a/mcp/package-lock.json b/mcp/package-lock.json
index 66e196483..c798ccb3b 100644
--- a/mcp/package-lock.json
+++ b/mcp/package-lock.json
@@ -24,7 +24,8 @@
"semver": "^7.8.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
- "typescript-eslint": "^8.59.2"
+ "typescript-eslint": "^8.59.2",
+ "vitest": "^2.1.9"
},
"engines": {
"node": ">=24.6.0"
@@ -777,6 +778,13 @@
"url": "https://github.com/sponsors/nzakas"
}
},
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved":
"https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity":
"sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.29.0",
"resolved":
"https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
@@ -817,6 +825,395 @@
}
}
},
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz",
+ "integrity":
"sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz",
+ "integrity":
"sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz",
+ "integrity":
"sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz",
+ "integrity":
"sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz",
+ "integrity":
"sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz",
+ "integrity":
"sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz",
+ "integrity":
"sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz",
+ "integrity":
"sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz",
+ "integrity":
"sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz",
+ "integrity":
"sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz",
+ "integrity":
"sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz",
+ "integrity":
"sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz",
+ "integrity":
"sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz",
+ "integrity":
"sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz",
+ "integrity":
"sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz",
+ "integrity":
"sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz",
+ "integrity":
"sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz",
+ "integrity":
"sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz",
+ "integrity":
"sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz",
+ "integrity":
"sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz",
+ "integrity":
"sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz",
+ "integrity":
"sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz",
+ "integrity":
"sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz",
+ "integrity":
"sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.62.2",
+ "resolved":
"https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz",
+ "integrity":
"sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
"node_modules/@types/estree": {
"version": "1.0.9",
"resolved":
"https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
@@ -1074,26 +1471,139 @@
"url": "https://opencollective.com/eslint"
}
},
- "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==",
+ "node_modules/@vitest/expect": {
+ "version": "2.1.9",
+ "resolved":
"https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
+ "integrity":
"sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
+ "dev": true,
"license": "MIT",
"dependencies": {
- "mime-types": "^3.0.0",
- "negotiator": "^1.0.0"
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "tinyrainbow": "^1.2.0"
},
- "engines": {
- "node": ">= 0.6"
+ "funding": {
+ "url": "https://opencollective.com/vitest"
}
},
- "node_modules/acorn": {
- "version": "8.17.0",
- "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
- "integrity":
"sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+ "node_modules/@vitest/mocker": {
+ "version": "2.1.9",
+ "resolved":
"https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
+ "integrity":
"sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
"dev": true,
"license": "MIT",
- "bin": {
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.12"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "2.1.9",
+ "resolved":
"https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
+ "integrity":
"sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "2.1.9",
+ "resolved":
"https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
+ "integrity":
"sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "2.1.9",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "2.1.9",
+ "resolved":
"https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
+ "integrity":
"sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.9",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
+ "integrity":
"sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^3.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
+ "integrity":
"sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.9",
+ "loupe": "^3.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz",
+ "integrity":
"sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-types": "^3.0.0",
+ "negotiator": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.17.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz",
+ "integrity":
"sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
"acorn": "bin/acorn"
},
"engines": {
@@ -1166,6 +1676,16 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved":
"https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity":
"sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved":
"https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@@ -1222,6 +1742,16 @@
"node": ">= 0.8"
}
},
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity":
"sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved":
"https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
@@ -1261,6 +1791,23 @@
"node": ">=6"
}
},
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity":
"sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1278,6 +1825,16 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved":
"https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity":
"sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved":
"https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -1393,6 +1950,16 @@
}
}
},
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity":
"sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -1468,6 +2035,13 @@
"node": ">= 0.4"
}
},
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved":
"https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity":
"sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved":
"https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
@@ -1776,6 +2350,16 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved":
"https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity":
"sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -1816,6 +2400,16 @@
"node": ">=18.0.0"
}
},
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved":
"https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity":
"sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
"node_modules/express": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
@@ -2421,6 +3015,23 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity":
"sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved":
"https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity":
"sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved":
"https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -2498,6 +3109,25 @@
"integrity":
"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
+ "node_modules/nanoid": {
+ "version": "3.3.15",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz",
+ "integrity":
"sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved":
"https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -2657,6 +3287,30 @@
"url": "https://opencollective.com/express"
}
},
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity":
"sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity":
"sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved":
"https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity":
"sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
"node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
@@ -2679,6 +3333,35 @@
"node": ">=16.20.0"
}
},
+ "node_modules/postcss": {
+ "version": "8.5.16",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz",
+ "integrity":
"sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved":
"https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -2786,6 +3469,51 @@
"node": ">=4"
}
},
+ "node_modules/rollup": {
+ "version": "4.62.2",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz",
+ "integrity":
"sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.62.2",
+ "@rollup/rollup-android-arm64": "4.62.2",
+ "@rollup/rollup-darwin-arm64": "4.62.2",
+ "@rollup/rollup-darwin-x64": "4.62.2",
+ "@rollup/rollup-freebsd-arm64": "4.62.2",
+ "@rollup/rollup-freebsd-x64": "4.62.2",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.62.2",
+ "@rollup/rollup-linux-arm-musleabihf": "4.62.2",
+ "@rollup/rollup-linux-arm64-gnu": "4.62.2",
+ "@rollup/rollup-linux-arm64-musl": "4.62.2",
+ "@rollup/rollup-linux-loong64-gnu": "4.62.2",
+ "@rollup/rollup-linux-loong64-musl": "4.62.2",
+ "@rollup/rollup-linux-ppc64-gnu": "4.62.2",
+ "@rollup/rollup-linux-ppc64-musl": "4.62.2",
+ "@rollup/rollup-linux-riscv64-gnu": "4.62.2",
+ "@rollup/rollup-linux-riscv64-musl": "4.62.2",
+ "@rollup/rollup-linux-s390x-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-gnu": "4.62.2",
+ "@rollup/rollup-linux-x64-musl": "4.62.2",
+ "@rollup/rollup-openbsd-x64": "4.62.2",
+ "@rollup/rollup-openharmony-arm64": "4.62.2",
+ "@rollup/rollup-win32-arm64-msvc": "4.62.2",
+ "@rollup/rollup-win32-ia32-msvc": "4.62.2",
+ "@rollup/rollup-win32-x64-gnu": "4.62.2",
+ "@rollup/rollup-win32-x64-msvc": "4.62.2",
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/router": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
@@ -2965,6 +3693,30 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity":
"sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved":
"https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity":
"sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity":
"sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
@@ -2974,6 +3726,13 @@
"node": ">= 0.8"
}
},
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity":
"sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved":
"https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
@@ -3000,6 +3759,20 @@
"node": ">=8"
}
},
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity":
"sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity":
"sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.17",
"resolved":
"https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
@@ -3017,6 +3790,36 @@
"url": "https://github.com/sponsors/SuperchupuDev"
}
},
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity":
"sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "1.2.0",
+ "resolved":
"https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+ "integrity":
"sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+ "integrity":
"sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved":
"https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
@@ -3185,19 +3988,615 @@
"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",
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity":
"sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
"dependencies": {
- "isexe": "^2.0.0"
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
},
"bin": {
- "node-which": "bin/node-which"
+ "vite": "bin/vite.js"
},
"engines": {
- "node": ">= 8"
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz",
+ "integrity":
"sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.7",
+ "es-module-lexer": "^1.5.4",
+ "pathe": "^1.1.2",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity":
"sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity":
"sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity":
"sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity":
"sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity":
"sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity":
"sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity":
"sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity":
"sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity":
"sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity":
"sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity":
"sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity":
"sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity":
"sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity":
"sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity":
"sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity":
"sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity":
"sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity":
"sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity":
"sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity":
"sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity":
"sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity":
"sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved":
"https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity":
"sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity":
"sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz",
+ "integrity":
"sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "2.1.9",
+ "@vitest/mocker": "2.1.9",
+ "@vitest/pretty-format": "^2.1.9",
+ "@vitest/runner": "2.1.9",
+ "@vitest/snapshot": "2.1.9",
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "debug": "^4.3.7",
+ "expect-type": "^1.1.0",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2",
+ "std-env": "^3.8.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.1",
+ "tinypool": "^1.0.1",
+ "tinyrainbow": "^1.2.0",
+ "vite": "^5.0.0",
+ "vite-node": "2.1.9",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "2.1.9",
+ "@vitest/ui": "2.1.9",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity":
"sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved":
"https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity":
"sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
}
},
"node_modules/word-wrap": {
diff --git a/mcp/package.json b/mcp/package.json
index 27166ea59..75a80c6fd 100644
--- a/mcp/package.json
+++ b/mcp/package.json
@@ -5,9 +5,14 @@
"type": "module",
"main": "dist/index.js",
"scripts": {
+ "build:validator": "node tools/build-validator.js",
"build": "tsc",
+ "build:all": "npm run build && npm run build:validator",
+ "check:plugin-manifests": "node tools/check-plugin-manifests.js",
"start": "node dist/index.js",
"dev": "tsx src/index.ts",
+ "test": "vitest run",
+ "test:watch": "vitest",
"lint": "eslint src/**/*.ts",
"format": "prettier --write src/**/*.ts",
"format:check": "prettier --check src/**/*.ts"
@@ -36,7 +41,8 @@
"semver": "^7.8.0",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
- "typescript-eslint": "^8.59.2"
+ "typescript-eslint": "^8.59.2",
+ "vitest": "^2.1.9"
},
"engines": {
"node": ">=24.6.0"
diff --git a/mcp/src/index.ts b/mcp/src/index.ts
index 7eec4341d..6336aa176 100644
--- a/mcp/src/index.ts
+++ b/mcp/src/index.ts
@@ -24,7 +24,7 @@ import { isLoopbackHost, startHttpServer } from
'./server/http.js';
import { createMcpServer } from './server/mcp.js';
import { log, setupGlobalErrorHandlers } from './utils/logger.js';
-dotenv.config();
+dotenv.config({ quiet: true });
setupGlobalErrorHandlers();
async function main() {
diff --git a/mcp/src/query/parse-validator.test.ts
b/mcp/src/query/parse-validator.test.ts
new file mode 100644
index 000000000..9a83ed663
--- /dev/null
+++ b/mcp/src/query/parse-validator.test.ts
@@ -0,0 +1,79 @@
+/**
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership. Apache Software
+ * Foundation (ASF) licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { spawnSync } from 'node:child_process';
+import { existsSync } from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+import { describe, expect, it } from 'vitest';
+
+import { validateBydbQLSyntax, validatorBinaryPath } from
'./parse-validator.js';
+
+const integrationTimeoutMs = 30000;
+const mcpRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)),
'..', '..');
+
+// Ensure the prebuilt validator binary exists so the spawn round-trip is
+// exercised. If the Go toolchain is unavailable the build is skipped and the
+// suite is skipped gracefully rather than failing.
+function ensureValidatorBinary(): boolean {
+ if (existsSync(validatorBinaryPath())) {
+ return true;
+ }
+ const build = spawnSync('node', ['tools/build-validator.js'], {
+ cwd: mcpRoot,
+ stdio: 'ignore',
+ timeout: integrationTimeoutMs,
+ });
+ return build.status === 0 && existsSync(validatorBinaryPath());
+}
+
+const binaryAvailable = ensureValidatorBinary();
+const describeIntegration = binaryAvailable ? describe : describe.skip;
+
+describeIntegration('validateBydbQLSyntax (spawn integration)', () => {
+ it(
+ 'round-trips a valid query through the built binary',
+ async () => {
+ const result = await validateBydbQLSyntax("SELECT * FROM STREAM sw IN
default TIME > '-30m'");
+ expect(result.valid).toBe(true);
+ expect(result.syntaxOnly).toBe(true);
+ expect(result.queryType?.toUpperCase()).toBe('STREAM');
+ },
+ integrationTimeoutMs,
+ );
+
+ it(
+ 'reports SHOW TOP queries as TOPN',
+ async () => {
+ const result = await validateBydbQLSyntax("SHOW TOP 10 FROM MEASURE m IN
g TIME > '-30m' ORDER BY DESC");
+ expect(result.valid).toBe(true);
+ expect(result.queryType?.toUpperCase()).toBe('TOPN');
+ },
+ integrationTimeoutMs,
+ );
+
+ it(
+ 'returns valid=false for a query that fails to parse',
+ async () => {
+ const result = await validateBydbQLSyntax('DELETE FROM STREAM sw IN
default');
+ expect(result.valid).toBe(false);
+ expect(result.syntaxOnly).toBe(true);
+ },
+ integrationTimeoutMs,
+ );
+});
diff --git a/mcp/src/query/parse-validator.ts b/mcp/src/query/parse-validator.ts
new file mode 100644
index 000000000..5da2bb1f8
--- /dev/null
+++ b/mcp/src/query/parse-validator.ts
@@ -0,0 +1,123 @@
+/**
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership. Apache Software
+ * Foundation (ASF) licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { spawn } from 'node:child_process';
+import { existsSync } from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const validatorTimeoutMs = 10000;
+const validatorBinaryName = process.platform === 'win32' ? 'bydbql-parse.exe'
: 'bydbql-parse';
+
+export type BydbQLParseValidationResult = {
+ valid: boolean;
+ message: string;
+ queryType?: string;
+ syntaxOnly: boolean;
+ warnings?: string[];
+};
+
+function mcpRootDir(): string {
+ const modulePath = fileURLToPath(import.meta.url);
+ return path.resolve(path.dirname(modulePath), '..', '..');
+}
+
+// Resolves the prebuilt validator binary path. The binary is a build artifact
+// produced by `npm run build:validator`; it is intentionally not shelled out
to
+// `go run` at query time so the plugin's runtime does not depend on a Go
+// toolchain and does not pay a cold-compile cost inside the validation
timeout.
+export function validatorBinaryPath(rootDir: string = mcpRootDir()): string {
+ return path.join(rootDir, 'tools', 'bin', validatorBinaryName);
+}
+
+export async function validateBydbQLSyntax(query: string):
Promise<BydbQLParseValidationResult> {
+ const binaryPath = validatorBinaryPath();
+ if (!existsSync(binaryPath)) {
+ throw new Error(
+ `BydbQL validator binary not found at ${binaryPath}. ` +
+ 'Build it once with `npm run build:validator` (requires the Go
toolchain) before starting the MCP server.',
+ );
+ }
+
+ return executeValidator(binaryPath, query);
+}
+
+async function executeValidator(binaryPath: string, query: string):
Promise<BydbQLParseValidationResult> {
+ return new Promise((resolve, reject) => {
+ const childProcess = spawn(binaryPath, [], {
+ cwd: path.dirname(binaryPath),
+ env: process.env,
+ stdio: ['pipe', 'pipe', 'pipe'],
+ });
+
+ let stdout = '';
+ let stderr = '';
+ let completed = false;
+
+ const timeout = setTimeout(() => {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ childProcess.kill('SIGKILL');
+ reject(new Error('BydbQL parser validation timed out'));
+ }, validatorTimeoutMs);
+
+ childProcess.stdout.on('data', (chunk: Buffer) => {
+ stdout += chunk.toString('utf-8');
+ });
+
+ childProcess.stderr.on('data', (chunk: Buffer) => {
+ stderr += chunk.toString('utf-8');
+ });
+
+ childProcess.on('error', (error) => {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ clearTimeout(timeout);
+ reject(error);
+ });
+
+ childProcess.on('close', (code) => {
+ if (completed) {
+ return;
+ }
+ completed = true;
+ clearTimeout(timeout);
+
+ if (!stdout.trim()) {
+ reject(new Error(stderr.trim() || `BydbQL parser exited with code
${code ?? 'unknown'}`));
+ return;
+ }
+
+ try {
+ resolve(JSON.parse(stdout) as BydbQLParseValidationResult);
+ } catch (error) {
+ reject(
+ new Error(
+ `failed to parse BydbQL parser response: ${error instanceof Error
? error.message : String(error)}\n${stdout}`,
+ ),
+ );
+ }
+ });
+
+ childProcess.stdin.write(JSON.stringify({ query }));
+ childProcess.stdin.end();
+ });
+}
diff --git a/mcp/src/query/validation.test.ts b/mcp/src/query/validation.test.ts
new file mode 100644
index 000000000..faa6f7f5b
--- /dev/null
+++ b/mcp/src/query/validation.test.ts
@@ -0,0 +1,62 @@
+/**
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership. Apache Software
+ * Foundation (ASF) licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { describe, expect, it } from 'vitest';
+
+import { validateBydbQL } from './validation.js';
+
+describe('validateBydbQL (read-only first gate)', () => {
+ it('accepts a SELECT query and returns the trimmed value', () => {
+ expect(validateBydbQL(" SELECT * FROM STREAM sw IN default TIME > '-30m'
")).toBe(
+ "SELECT * FROM STREAM sw IN default TIME > '-30m'",
+ );
+ });
+
+ it('accepts a SHOW TOP query (case-insensitive prefix)', () => {
+ expect(validateBydbQL("show top 10 FROM MEASURE m IN g TIME > '-30m' ORDER
BY DESC")).toBe(
+ "show top 10 FROM MEASURE m IN g TIME > '-30m' ORDER BY DESC",
+ );
+ });
+
+ it('rejects non-read-only statements', () => {
+ expect(() => validateBydbQL('DELETE FROM STREAM sw IN default')).toThrow(
+ 'BydbQL must be a read-only SELECT or SHOW TOP query',
+ );
+ expect(() => validateBydbQL('DROP STREAM sw')).toThrow('BydbQL must be a
read-only SELECT or SHOW TOP query');
+ });
+
+ it('rejects statement terminators and comment syntax', () => {
+ expect(() => validateBydbQL('SELECT * FROM STREAM sw IN
default;')).toThrow(
+ 'BydbQL contains unsupported multi-statement or comment syntax',
+ );
+ expect(() => validateBydbQL('SELECT * FROM STREAM sw IN default --
comment')).toThrow(
+ 'BydbQL contains unsupported multi-statement or comment syntax',
+ );
+ expect(() => validateBydbQL('SELECT * FROM STREAM sw IN default /* comment
*/')).toThrow(
+ 'BydbQL contains unsupported multi-statement or comment syntax',
+ );
+ });
+
+ it('rejects empty input', () => {
+ expect(() => validateBydbQL(' ')).toThrow('BydbQL cannot be empty');
+ });
+
+ it('rejects over-length input (> 4096 characters)', () => {
+ const overLength = `SELECT * FROM STREAM sw IN default WHERE service =
'${'a'.repeat(4096)}'`;
+ expect(() => validateBydbQL(overLength)).toThrow('BydbQL exceeds the
maximum length of 4096 characters');
+ });
+});
diff --git a/mcp/src/query/validation.ts b/mcp/src/query/validation.ts
index 2d91811c4..bd3ca12a9 100644
--- a/mcp/src/query/validation.ts
+++ b/mcp/src/query/validation.ts
@@ -96,7 +96,7 @@ function validateQueryHintResourceType(rawValue: string):
(typeof allowedQueryHi
return value as (typeof allowedQueryHintResourceTypes)[number];
}
-function validateBydbQL(rawValue: string): string {
+export function validateBydbQL(rawValue: string): string {
const value = validateTextInput('BydbQL', rawValue, maxBydbQLLength);
if (!allowedQueryPrefixPatterns.some((pattern) => pattern.test(value))) {
throw new Error('BydbQL must be a read-only SELECT or SHOW TOP query');
diff --git a/mcp/src/server/mcp.ts b/mcp/src/server/mcp.ts
index 1f237061b..f6fcdb605 100644
--- a/mcp/src/server/mcp.ts
+++ b/mcp/src/server/mcp.ts
@@ -23,6 +23,8 @@ import { BanyanDBClient, ResourceMetadata } from
'../client/index.js';
import { MAX_TOOL_RESPONSE_LENGTH } from '../config.js';
import { loadQueryContext } from '../query/context.js';
import { generateBydbQL } from '../query/llm-prompt.js';
+import type { BydbQLParseValidationResult } from '../query/parse-validator.js';
+import { validateBydbQLSyntax } from '../query/parse-validator.js';
import { normalizeQueryHints, validateListGroupsArgs, validateQueryHints }
from '../query/validation.js';
export function truncateText(text: string, maxLength: number): string {
@@ -126,6 +128,21 @@ function buildListToolsResponse() {
required: ['description'],
},
},
+ {
+ name: 'validate_bydbql',
+ description:
+ 'Validate a BydbQL query with read-only safety checks and parse-only
syntax validation. This does not execute the query or verify live BanyanDB
schema existence.',
+ inputSchema: {
+ type: 'object',
+ properties: {
+ BydbQL: {
+ type: 'string',
+ description: 'BydbQL query to validate.',
+ },
+ },
+ required: ['BydbQL'],
+ },
+ },
],
};
}
@@ -257,6 +274,34 @@ async function
handleGetGenerateBydbqlPrompt(banyandbClient: BanyanDBClient, arg
return { content: [{ type: 'text', text: prompt }] };
}
+function validationResultText(result: BydbQLParseValidationResult): string {
+ return JSON.stringify(result, null, 2);
+}
+
+async function handleValidateBydbql(args: unknown) {
+ const normalizedHints = normalizeQueryHints(args);
+ if (!normalizedHints.BydbQL) {
+ throw new Error('BydbQL is required');
+ }
+
+ try {
+ const queryHints = validateQueryHints(normalizedHints);
+ if (!queryHints.BydbQL) {
+ throw new Error('BydbQL is required');
+ }
+
+ const result = await validateBydbQLSyntax(queryHints.BydbQL);
+ return { content: [{ type: 'text', text: validationResultText(result) }] };
+ } catch (error) {
+ const result: BydbQLParseValidationResult = {
+ valid: false,
+ message: error instanceof Error ? error.message : String(error),
+ syntaxOnly: true,
+ };
+ return { content: [{ type: 'text', text: validationResultText(result) }] };
+ }
+}
+
export function createMcpServer(banyandbClient: BanyanDBClient): McpServer {
const server = new McpServer(
{
@@ -333,6 +378,9 @@ export function createMcpServer(banyandbClient:
BanyanDBClient): McpServer {
if (name === 'get_generate_bydbql_prompt') {
return handleGetGenerateBydbqlPrompt(banyandbClient, args);
}
+ if (name === 'validate_bydbql') {
+ return handleValidateBydbql(args);
+ }
throw new Error(`Unknown tool: ${name}`);
});
diff --git a/mcp/tools/build-validator.js b/mcp/tools/build-validator.js
new file mode 100644
index 000000000..0c32d7aed
--- /dev/null
+++ b/mcp/tools/build-validator.js
@@ -0,0 +1,44 @@
+/**
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership. Apache Software
+ * Foundation (ASF) licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { spawnSync } from 'node:child_process';
+import { mkdirSync } from 'node:fs';
+import { tmpdir } from 'node:os';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const toolsDir = path.dirname(fileURLToPath(import.meta.url));
+const mcpRootDir = path.resolve(toolsDir, '..');
+const binaryName = process.platform === 'win32' ? 'bydbql-parse.exe' :
'bydbql-parse';
+const outputDir = path.join(toolsDir, 'bin');
+const outputPath = path.join(outputDir, binaryName);
+
+mkdirSync(outputDir, { recursive: true });
+
+const result = spawnSync('go', ['build', '-o', outputPath,
'./tools/bydbql-parse'], {
+ cwd: mcpRootDir,
+ env: {
+ ...process.env,
+ GOCACHE: process.env.GOCACHE || path.join(tmpdir(),
'banyandb-mcp-go-build-cache'),
+ GOMODCACHE: process.env.GOMODCACHE || path.join(tmpdir(),
'banyandb-mcp-go-mod-cache'),
+ GOPATH: process.env.GOPATH || path.join(tmpdir(), 'banyandb-mcp-go-path'),
+ GOFLAGS: process.env.GOFLAGS || '-mod=readonly',
+ },
+ stdio: 'inherit',
+});
+
+process.exit(result.status ?? 1);
diff --git a/mcp/tools/bydbql-parse/main.go b/mcp/tools/bydbql-parse/main.go
new file mode 100644
index 000000000..75f518844
--- /dev/null
+++ b/mcp/tools/bydbql-parse/main.go
@@ -0,0 +1,96 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with this work for
+// additional information regarding copyright ownership. Apache Software
+// Foundation (ASF) licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except in
+// compliance with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "encoding/json"
+ "fmt"
+ "os"
+
+ "github.com/apache/skywalking-banyandb/pkg/bydbql"
+)
+
+type validateRequest struct {
+ Query string `json:"query"`
+}
+
+type validateResponse struct {
+ Valid bool `json:"valid"`
+ Message string `json:"message"`
+ QueryType string `json:"queryType,omitempty"`
+ SyntaxOnly bool `json:"syntaxOnly"`
+ Warnings []string `json:"warnings,omitempty"`
+}
+
+func main() {
+ var req validateRequest
+ decodeErr := json.NewDecoder(os.Stdin).Decode(&req)
+ if decodeErr != nil {
+ writeResponse(validateResponse{
+ Valid: false,
+ Message: fmt.Sprintf("failed to read validation
request: %v", decodeErr),
+ SyntaxOnly: true,
+ })
+ os.Exit(1)
+ }
+
+ writeResponse(validate(req.Query))
+}
+
+// validate performs parse-only BydbQL syntax validation and reports the
+// detected query type. It never executes the query or verifies live schema
+// existence, so callers can unit-test the parse contract without a database.
+func validate(query string) validateResponse {
+ grammar, parseErr := bydbql.ParseQuery(query)
+ if parseErr != nil {
+ return validateResponse{
+ Valid: false,
+ Message: fmt.Sprintf("failed to parse BydbQL: %v",
parseErr),
+ SyntaxOnly: true,
+ }
+ }
+
+ return validateResponse{
+ Valid: true,
+ Message: "valid BydbQL syntax",
+ QueryType: queryType(grammar),
+ SyntaxOnly: true,
+ Warnings: []string{
+ "parse-only validation does not verify group, resource,
tag, field, or index-rule existence",
+ },
+ }
+}
+
+func queryType(grammar *bydbql.Grammar) string {
+ if grammar == nil {
+ return ""
+ }
+ if grammar.TopN != nil {
+ return "TOPN"
+ }
+ if grammar.Select != nil && grammar.Select.From != nil {
+ return grammar.Select.From.ResourceType
+ }
+ return ""
+}
+
+func writeResponse(resp validateResponse) {
+ encodeErr := json.NewEncoder(os.Stdout).Encode(resp)
+ if encodeErr != nil {
+ _, _ = fmt.Fprintf(os.Stderr, "failed to write validation
response: %v\n", encodeErr)
+ os.Exit(1)
+ }
+}
diff --git a/mcp/tools/bydbql-parse/main_test.go
b/mcp/tools/bydbql-parse/main_test.go
new file mode 100644
index 000000000..89adf207f
--- /dev/null
+++ b/mcp/tools/bydbql-parse/main_test.go
@@ -0,0 +1,114 @@
+// Licensed to Apache Software Foundation (ASF) under one or more contributor
+// license agreements. See the NOTICE file distributed with this work for
+// additional information regarding copyright ownership. Apache Software
+// Foundation (ASF) licenses this file to you under the Apache License,
+// Version 2.0 (the "License"); you may not use this file except in
+// compliance with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+ "bufio"
+ "os"
+ "path/filepath"
+ "strings"
+ "testing"
+)
+
+func TestValidateContract(t *testing.T) {
+ tests := []struct {
+ name string
+ query string
+ wantValid bool
+ wantQueryType string
+ }{
+ {name: "select stream", query: "SELECT * FROM STREAM sw IN
default TIME > '-30m'", wantValid: true, wantQueryType: "STREAM"},
+ {name: "select measure", query: "SELECT service, MEAN(latency)
FROM MEASURE m IN g TIME > '-30m' GROUP BY service", wantValid: true,
wantQueryType: "MEASURE"},
+ {name: "select trace", query: "SELECT () FROM TRACE sw_trace IN
default TIME > '-30m'", wantValid: true, wantQueryType: "TRACE"},
+ {name: "select property", query: "SELECT * FROM PROPERTY
server_metadata IN datacenter-1 WHERE ID = 'server-1'", wantValid: true,
wantQueryType: "PROPERTY"},
+ {name: "show top", query: "SHOW TOP 10 FROM MEASURE m IN g TIME
> '-30m' AGGREGATE BY MEAN ORDER BY DESC", wantValid: true, wantQueryType:
"TOPN"},
+ {name: "reject delete", query: "DELETE FROM STREAM sw IN
default", wantValid: false},
+ {name: "reject trailing semicolon", query: "SELECT * FROM
STREAM sw IN default;", wantValid: false},
+ {name: "reject line comment", query: "SELECT * FROM STREAM sw
IN default -- comment", wantValid: false},
+ {name: "reject two statements", query: "SELECT * FROM STREAM sw
IN default SELECT * FROM STREAM sw IN default", wantValid: false},
+ {name: "reject empty input", query: "", wantValid: false},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ resp := validate(tt.query)
+ if resp.Valid != tt.wantValid {
+ t.Fatalf("validate(%q).Valid = %t, want %t
(message: %s)", tt.query, resp.Valid, tt.wantValid, resp.Message)
+ }
+ if !resp.SyntaxOnly {
+ t.Errorf("validate(%q).SyntaxOnly = false, want
true", tt.query)
+ }
+ if !tt.wantValid {
+ return
+ }
+ if !strings.EqualFold(resp.QueryType, tt.wantQueryType)
{
+ t.Errorf("validate(%q).QueryType = %q, want %q
(case-insensitive)", tt.query, resp.QueryType, tt.wantQueryType)
+ }
+ if len(resp.Warnings) == 0 {
+ t.Errorf("validate(%q) returned no parse-only
warning", tt.query)
+ }
+ })
+ }
+}
+
+func TestValidateGoldenCorpus(t *testing.T) {
+ for _, corpus := range []struct {
+ file string
+ wantValid bool
+ }{
+ {file: "valid.txt", wantValid: true},
+ {file: "invalid.txt", wantValid: false},
+ } {
+ queries := readCorpus(t, filepath.Join("testdata", corpus.file))
+ if len(queries) == 0 {
+ t.Fatalf("corpus %s is empty", corpus.file)
+ }
+ for _, query := range queries {
+ resp := validate(query)
+ if resp.Valid != corpus.wantValid {
+ t.Errorf("[%s] validate(%q).Valid = %t, want %t
(message: %s)", corpus.file, query, resp.Valid, corpus.wantValid, resp.Message)
+ }
+ }
+ }
+}
+
+func readCorpus(t *testing.T, path string) []string {
+ t.Helper()
+ file, openErr := os.Open(path)
+ if openErr != nil {
+ t.Fatalf("failed to open corpus %s: %v", path, openErr)
+ }
+ defer func() {
+ if closeErr := file.Close(); closeErr != nil {
+ t.Errorf("failed to close corpus %s: %v", path,
closeErr)
+ }
+ }()
+
+ var queries []string
+ scanner := bufio.NewScanner(file)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+ queries = append(queries, line)
+ }
+ if scanErr := scanner.Err(); scanErr != nil {
+ t.Fatalf("failed to read corpus %s: %v", path, scanErr)
+ }
+
+ return queries
+}
diff --git a/mcp/tools/bydbql-parse/testdata/invalid.txt
b/mcp/tools/bydbql-parse/testdata/invalid.txt
new file mode 100644
index 000000000..9b76ff85d
--- /dev/null
+++ b/mcp/tools/bydbql-parse/testdata/invalid.txt
@@ -0,0 +1,22 @@
+# Curated inputs that MUST be rejected by parse-only validation.
+# One input per line. Blank lines and lines starting with '#' are ignored.
+# Empty-string input is covered separately by the unit test.
+
+# Not a read-only SELECT / SHOW TOP statement.
+DELETE FROM STREAM sw IN default
+INSERT INTO STREAM sw IN default
+
+# Multi-statement / statement terminators are not part of the grammar.
+SELECT * FROM STREAM sw IN default;
+SELECT * FROM STREAM sw IN default SELECT * FROM STREAM sw IN default
+
+# SQL comment syntax is not tokenizable.
+SELECT * FROM STREAM sw IN default -- trailing comment
+SELECT * FROM STREAM sw IN default /* block comment */
+
+# Structural errors.
+SELECT
+FROM STREAM sw IN default
+SELECT * FROM STREAM sw
+SELECT * FROM PLANET sw IN default
+SHOW TOP FROM MEASURE service_latency IN production
diff --git a/mcp/tools/bydbql-parse/testdata/valid.txt
b/mcp/tools/bydbql-parse/testdata/valid.txt
new file mode 100644
index 000000000..027ea0e28
--- /dev/null
+++ b/mcp/tools/bydbql-parse/testdata/valid.txt
@@ -0,0 +1,28 @@
+# Curated BydbQL statements that MUST parse successfully.
+# One statement per line. Blank lines and lines starting with '#' are ignored.
+# Do not scrape the skill docs here: those contain placeholders such as
+# <stream_name> and clause fragments that intentionally do not parse.
+
+# STREAM
+SELECT * FROM STREAM sw IN default
+SELECT * FROM STREAM logs IN default TIME > '-30m' WHERE message
MATCH('error') LIMIT 100
+SELECT trace_id, service_id FROM STREAM sw IN group1, group2 TIME BETWEEN
'-2h' AND 'now' WHERE service_id = 'webapp' ORDER BY start_time DESC LIMIT 100
+SELECT * FROM STREAM sw IN default ON warn, cold STAGES TIME > '-30m'
+SELECT * FROM STREAM logs IN default TIME > '-30m' WITH QUERY_TRACE
+
+# MEASURE
+SELECT * FROM MEASURE service_cpm_minute IN metricsMinute TIME > '-3d'
+SELECT service, MEAN(response_time) FROM MEASURE http_metrics IN metricsMinute
TIME > '-30m' WHERE region = 'us-west' GROUP BY service
+SELECT status::TAG, status::FIELD FROM MEASURE http_requests IN default TIME >
'-30m' WHERE path = '/api/v1/users'
+
+# TRACE
+SELECT () FROM TRACE sw_trace IN default TIME > '-30m' WHERE status = 'error'
LIMIT 50
+SELECT trace_id, operation_name FROM TRACE zipkin_span IN default TIME >
'-30m' WHERE operation_name MATCH(('GET', 'POST'), 'keyword') ORDER BY
timestamp_millis DESC LIMIT 30
+
+# PROPERTY (no TIME clause)
+SELECT * FROM PROPERTY server_metadata IN datacenter-1 WHERE ID =
'server-1a2b3c'
+SELECT ip, region FROM PROPERTY server_metadata IN datacenter-1 WHERE ID IN
('server-1', 'server-2') LIMIT 10
+
+# SHOW TOP
+SHOW TOP 10 FROM MEASURE service_latency IN production TIME > '-30m' WHERE
http_method = 'GET' AGGREGATE BY MEAN ORDER BY DESC
+SHOW TOP 5 FROM MEASURE endpoint_errors IN production TIME BETWEEN '-24h' AND
'now' WHERE status_code = '500' AGGREGATE BY SUM ORDER BY DESC
diff --git a/mcp/tools/check-plugin-manifests.js
b/mcp/tools/check-plugin-manifests.js
new file mode 100644
index 000000000..bd5eaf91a
--- /dev/null
+++ b/mcp/tools/check-plugin-manifests.js
@@ -0,0 +1,88 @@
+/**
+ * Licensed to Apache Software Foundation (ASF) under one or more contributor
+ * license agreements. See the NOTICE file distributed with this work for
+ * additional information regarding copyright ownership. Apache Software
+ * Foundation (ASF) licenses this file to you under the Apache License,
+ * Version 2.0 (the "License"); you may not use this file except in
+ * compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { readFileSync } from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const toolsDir = path.dirname(fileURLToPath(import.meta.url));
+const repoRootDir = path.resolve(toolsDir, '..', '..');
+const claudeManifestPath = path.join(repoRootDir, '.claude-plugin',
'plugin.json');
+const codexManifestPath = path.join(repoRootDir, '.codex-plugin',
'plugin.json');
+
+function readManifest(manifestPath) {
+ return JSON.parse(readFileSync(manifestPath, 'utf8'));
+}
+
+function withoutVersion(manifest) {
+ const normalizedManifest = { ...manifest };
+ delete normalizedManifest.version;
+ return normalizedManifest;
+}
+
+function normalize(value) {
+ if (Array.isArray(value)) {
+ return value.map((item) => normalize(item));
+ }
+ if (value !== null && typeof value === 'object') {
+ return Object.keys(value)
+ .sort()
+ .reduce((normalizedValue, key) => {
+ normalizedValue[key] = normalize(value[key]);
+ return normalizedValue;
+ }, {});
+ }
+ return value;
+}
+
+function stableJSON(value) {
+ return JSON.stringify(normalize(value));
+}
+
+const claudeManifest = readManifest(claudeManifestPath);
+const codexManifest = readManifest(codexManifestPath);
+let hasError = false;
+
+function reportError(message) {
+ console.error(`ERROR: ${message}`);
+ hasError = true;
+}
+
+if (stableJSON(withoutVersion(claudeManifest)) !==
stableJSON(withoutVersion(codexManifest))) {
+ reportError('.claude-plugin/plugin.json and .codex-plugin/plugin.json must
match except for version.');
+}
+
+if (typeof claudeManifest.version !== 'string') {
+ reportError('.claude-plugin/plugin.json must define a string version.');
+}
+
+if (typeof codexManifest.version !== 'string') {
+ reportError('.codex-plugin/plugin.json must define a string version.');
+} else {
+ const codexVersionParts = codexManifest.version.split('+');
+ const codexBaseVersion = codexVersionParts[0];
+ const codexBuildMetadata = codexVersionParts.slice(1).join('+');
+ if (codexBaseVersion !== claudeManifest.version ||
!codexBuildMetadata.startsWith('codex.')) {
+ reportError('.codex-plugin/plugin.json version must use the Claude version
plus a +codex.<timestamp> suffix.');
+ }
+}
+
+if (hasError) {
+ process.exit(1);
+}
+
+console.log('Plugin manifests are synchronized; only version differs.');
diff --git a/mcp/tsconfig.json b/mcp/tsconfig.json
index 4a9ef8044..9f389ebca 100644
--- a/mcp/tsconfig.json
+++ b/mcp/tsconfig.json
@@ -16,6 +16,6 @@
"sourceMap": true
},
"include": ["src/**/*"],
- "exclude": ["node_modules", "dist"]
+ "exclude": ["node_modules", "dist", "src/**/*.test.ts"]
}
diff --git a/skills/bydbql/SKILL.md b/skills/bydbql/SKILL.md
new file mode 100644
index 000000000..599008242
--- /dev/null
+++ b/skills/bydbql/SKILL.md
@@ -0,0 +1,39 @@
+---
+name: bydbql
+description: Generate, validate, and optionally execute read-only BanyanDB
BydbQL for STREAM, MEASURE, TRACE, and PROPERTY resources. Use when the user
asks to query BanyanDB, translate natural language to BydbQL, inspect BanyanDB
schema or data, validate BydbQL, or fetch raw BanyanDB records.
+---
+
+# BanyanDB BydbQL
+
+Use this skill when the user asks to query BanyanDB, generate BydbQL,
translate natural language to BydbQL, inspect BanyanDB data, validate BydbQL,
or run read-only BanyanDB queries.
+
+## Workflow
+
+1. Read `references/safety.md` before generating, validating, or executing
BydbQL.
+2. Read `references/syntax.md` when query syntax is needed.
+3. Read `references/examples.md` when mapping natural language to BydbQL.
+4. Identify the BanyanDB resource type: `STREAM`, `MEASURE`, `TRACE`, or
`PROPERTY`.
+5. If the group, resource name, or resource type is missing or ambiguous, use
`list_groups_schemas`.
+6. If the request involves sorting or ranking (`ORDER BY`, `SHOW TOP`, or
wording such as "latest", "top", "highest", "lowest", "first", "last"), or you
have already fetched schema, call `get_generate_bydbql_prompt` and follow its
index-rule guidance. This is required: `get_generate_bydbql_prompt` is the only
component that injects the indexed-field list and enforces `ORDER BY` field
substitution/omission. `validate_bydbql` is parse-only, so `ORDER BY
<non-indexed-field>` validates `true [...]
+7. Generate exactly one read-only BydbQL statement. When it contains `ORDER
BY`, only sort by a field from the indexed-field list returned by
`get_generate_bydbql_prompt`; substitute the closest indexed field, or omit
`ORDER BY` when no indexed field matches.
+8. Call `validate_bydbql` before execution.
+9. Execute with `list_resources_bydbql` only when the user asks to run, query,
fetch, show, or inspect raw BanyanDB data.
+10. If `validate_bydbql` returns `valid=false`, fix the query and validate
again before any execution.
+11. If execution fails due to a missing group, resource, or schema, use
`list_groups_schemas` to discover the correct names and retry only when the
correction is clear.
+
+## Tool Use
+
+Use the existing BanyanDB MCP tools:
+
+- `list_groups_schemas`: discover groups and resource names for streams,
measures, traces, and properties.
+- `get_generate_bydbql_prompt`: return the generation prompt text, including
the live indexed-field list and the `ORDER BY` index-rule substitution/omission
rules. It is the only source of index-rule `ORDER BY` safety.
+- `validate_bydbql`: run read-only safety checks and parse-only BydbQL syntax
validation. This does not verify group, resource, tag, field, or index-rule
existence.
+- `list_resources_bydbql`: execute a validated BydbQL statement when data
retrieval is requested.
+
+The host model performs natural language to BydbQL translation using these
references and live schema discovery. Promote `get_generate_bydbql_prompt` to a
required step whenever ranking or sorting is involved (or once schema has been
fetched), because it carries the only index-rule `ORDER BY` check;
`validate_bydbql` is parse-only and cannot catch a sort on a non-indexed field.
+
+## Output
+
+For generation-only BydbQL requests, return the single BydbQL statement and
state that it was not executed.
+
+For BydbQL execution requests, show the BydbQL statement, then summarize the
returned data or error concisely.
diff --git a/skills/bydbql/references/examples.md
b/skills/bydbql/references/examples.md
new file mode 100644
index 000000000..a3a39dc5f
--- /dev/null
+++ b/skills/bydbql/references/examples.md
@@ -0,0 +1,30 @@
+# Natural Language Examples
+
+These examples map natural language to single read-only BydbQL statements.
+
+```text
+"Query service_cpm_minute in metricsMinute for the last 30 minutes"
+=> SELECT * FROM MEASURE service_cpm_minute IN metricsMinute TIME > '-30m'
+```
+
+```text
+"Find logs where service is frontend in the last 30 minutes"
+=> SELECT * FROM STREAM <stream_name> IN <group> TIME > '-30m' WHERE service =
'frontend'
+```
+
+```text
+"Show the top 10 services by cpm over the last hour"
+=> SHOW TOP 10 FROM MEASURE service_cpm_minute IN metricsMinute TIME > '-1h'
AGGREGATE BY SUM ORDER BY DESC
+```
+
+```text
+"Show properties for server metadata in datacenter-1"
+=> SELECT * FROM PROPERTY server_metadata IN datacenter-1
+```
+
+```text
+"Find recent trace segments for trace ID abc123"
+=> SELECT trace_id, segment_id, service_id FROM TRACE segment IN sw_trace TIME
> '-1h' WHERE trace_id = 'abc123' LIMIT 50
+```
+
+Use `list_groups_schemas` to replace placeholders such as `<stream_name>` or
`<group>` before execution.
diff --git a/skills/bydbql/references/safety.md
b/skills/bydbql/references/safety.md
new file mode 100644
index 000000000..20c997764
--- /dev/null
+++ b/skills/bydbql/references/safety.md
@@ -0,0 +1,18 @@
+# Safety Rules
+
+Only generate read-only BydbQL.
+
+Allowed prefixes:
+
+- `SELECT`
+- `SHOW TOP`
+
+Never generate multiple statements.
+
+Never include semicolons.
+
+Never include SQL comments such as `--`, `/*`, or `*/`.
+
+Validate with `validate_bydbql` before execution.
+
+Parse-only validation does not prove that groups, resources, tags, fields, or
index rules exist. Use `list_groups_schemas` when names are missing, ambiguous,
or rejected by execution.
diff --git a/skills/bydbql/references/syntax.md
b/skills/bydbql/references/syntax.md
new file mode 100644
index 000000000..9eac9a5c9
--- /dev/null
+++ b/skills/bydbql/references/syntax.md
@@ -0,0 +1,374 @@
+# BydbQL Syntax Reference
+
+Use this reference to generate one read-only BanyanDB Query Language statement
from natural language. Keep dynamic names such as groups, resource names, tags,
fields, and sortable fields aligned with live BanyanDB schema discovered
through MCP.
+
+## Core Rules
+
+- Statement forms: `SELECT ... FROM ...` or `SHOW TOP ... FROM MEASURE ...`.
+- Resource types: `STREAM`, `MEASURE`, `TRACE`, `PROPERTY`.
+- `FROM <RESOURCE_TYPE> <resource_name> IN <group>[, <group>...]` is required.
+- Parentheses around group lists are optional: `IN default, staging` and `IN
(default, staging)` are both valid.
+- `TIME` is strongly recommended for `STREAM`, `MEASURE`, `TRACE`, and `SHOW
TOP` queries and is normally expected at execution, but the grammar treats it
as optional, so the parser (and `validate_bydbql`) will not flag its absence.
`PROPERTY` queries do not use `TIME`.
+- Keywords are case-insensitive. Identifiers are case-sensitive and should
match schema names exactly.
+- Identifiers may be simple (`service_id`), dotted (`http.method`), or quoted
when needed (`"count"`). String filter values should use single quotes.
+- Clause order for `SELECT`: `SELECT` -> `FROM` -> `TIME` -> `WHERE` -> `GROUP
BY` -> `ORDER BY` -> `WITH QUERY_TRACE` -> `LIMIT` -> `OFFSET`.
+- Clause order for `SHOW TOP`: `SHOW TOP` -> `FROM` -> `TIME` -> `WHERE` ->
`AGGREGATE BY` -> `ORDER BY` -> `WITH QUERY_TRACE`.
+
+## SELECT Form
+
+```sql
+SELECT <projection>
+FROM STREAM|MEASURE|TRACE|PROPERTY <name>
+IN <group>[, <group>...]
+[ON <stage>[, <stage>...] STAGES]
+[TIME <time_condition>]
+[WHERE <criteria>]
+[GROUP BY <tag>[, <tag>...]]
+[ORDER BY <field_or_TIME> [ASC|DESC] | ORDER BY ASC|DESC]
+[WITH QUERY_TRACE]
+[LIMIT <n>]
+[OFFSET <n>]
+```
+
+Projection forms:
+
+- `*`: all projected columns supported by the resource.
+- `()`: empty projection, useful for trace queries when only matching traces
are needed.
+- `tag_or_field`: schema-inferred tag or field.
+- `identifier::TAG` or `identifier::FIELD`: disambiguate a measure key that
can be both a tag and a field.
+- `SUM(field)`, `MEAN(field)`, `AVG(field)`, `COUNT(field)`, `MAX(field)`,
`MIN(field)`: measure aggregations.
+- `TOP <n> <order_field> [ASC|DESC], <other_columns...>`: SELECT-form top
query for measures/streams when supported by the transformer.
+
+## SHOW TOP Form
+
+```sql
+SHOW TOP <n>
+FROM MEASURE <name>
+IN <group>[, <group>...]
+[ON <stage>[, <stage>...] STAGES]
+TIME <time_condition>
+[WHERE <topn_criteria>]
+[AGGREGATE BY SUM|MEAN|AVG|COUNT|MAX|MIN]
+[ORDER BY ASC|DESC]
+[WITH QUERY_TRACE]
+```
+
+Use `SHOW TOP` for measure ranking requests such as "top 10", "highest",
"lowest", "best", or "worst" over a measure. `SHOW TOP` order direction has no
field name; use `ORDER BY DESC` for highest/top and `ORDER BY ASC` for
lowest/bottom.
+
+## TIME Clause
+
+Supported forms:
+
+```sql
+TIME = '<timestamp>'
+TIME > '<timestamp>'
+TIME < '<timestamp>'
+TIME >= '<timestamp>'
+TIME <= '<timestamp>'
+TIME BETWEEN '<begin>' AND '<end>'
+```
+
+Timestamp values:
+
+- Absolute RFC3339: `'2023-01-01T00:00:00Z'`, `'2023-01-01T15:30:45+08:00'`.
+- Relative: `'-30m'`, `'-2h'`, `'-1d'`, `'-1w'`, `'now'`.
+- Units: `m` minutes, `h` hours, `d` days, `w` weeks.
+
+Natural-language mapping:
+
+- "last 30 minutes", "past hour", "recent 1 day" -> `TIME > '-30m'`, `TIME >
'-1h'`, `TIME > '-1d'`.
+- "between X and Y" -> `TIME BETWEEN 'X' AND 'Y'`.
+- Time windows are not limits. Do not turn "last 3 days" into `LIMIT 3`.
+
+## WHERE Criteria
+
+General `WHERE` supports:
+
+```sql
+<identifier> = <value>
+<identifier> != <value>
+<identifier> > <value>
+<identifier> < <value>
+<identifier> >= <value>
+<identifier> <= <value>
+<identifier> IN (<value>[, <value>...])
+<identifier> NOT IN (<value>[, <value>...])
+<identifier> HAVING <value_or_list>
+<identifier> NOT HAVING <value_or_list>
+<identifier> MATCH(...)
+```
+
+Values:
+
+- String: `'frontend'`, `'/api/users'`.
+- Integer: `200`, `500`.
+- Null: `NULL`.
+- Lists: `('frontend', 'backend')`, `(200, 500)`.
+
+Logical composition:
+
+- Parentheses have highest precedence.
+- `AND` binds tighter than `OR`.
+- Use parentheses for mixed `AND`/`OR` whenever natural language is ambiguous.
+
+Examples:
+
+```sql
+WHERE service_id = 'webapp' AND status_code >= 500
+WHERE service_id IN ('frontend', 'backend')
+WHERE (status = 'error' OR status = 'warn') AND service = 'checkout'
+WHERE tags HAVING ('region', 'prod')
+```
+
+## MATCH Full-Text Search
+
+Use `MATCH` for text search in stream, measure, and trace tags/fields when
indexed for text analysis. Do not use `MATCH` for `SHOW TOP` or `PROPERTY`.
+
+Forms:
+
+```sql
+WHERE message MATCH('error')
+WHERE http_url MATCH('/api/users', 'url')
+WHERE message MATCH(('error', 'warning'), 'standard', 'OR')
+WHERE operation_name MATCH(('GET', 'POST'), 'keyword')
+```
+
+Parameters:
+
+- First parameter is one value or a parenthesized list.
+- Optional analyzer: `'standard'`, `'simple'`, `'keyword'`, `'url'`.
+- Optional operator for multiple values: `'AND'` or `'OR'`.
+
+Use `"keyword"` style matching for exact terms, `"standard"` for free text,
and `"url"` for URL/path fields.
+
+## Streams
+
+Streams query raw time-series elements such as logs and events.
+
+```sql
+SELECT <projection>
+FROM STREAM <stream_name> IN <group>[, <group>...]
+[ON <stage>[, <stage>...] STAGES]
+TIME <time_condition>
+[WHERE <criteria>]
+[ORDER BY <field_or_TIME> [ASC|DESC] | ORDER BY ASC|DESC]
+[WITH QUERY_TRACE]
+[LIMIT <n>]
+[OFFSET <n>]
+```
+
+Examples:
+
+```sql
+SELECT *
+FROM STREAM logs IN default
+TIME > '-30m'
+WHERE message MATCH('error')
+LIMIT 100
+```
+
+```sql
+SELECT trace_id, service_id
+FROM STREAM sw IN group1, group2
+TIME BETWEEN '-2h' AND 'now'
+WHERE service_id = 'webapp'
+ORDER BY start_time DESC
+LIMIT 100
+```
+
+## Measures
+
+Measures query numerical time-series metrics. They support tag/field
projection, aggregation, and grouping.
+
+```sql
+SELECT <tag_or_field_or_aggregation>[, ...]
+FROM MEASURE <measure_name> IN <group>[, <group>...]
+[ON <stage>[, <stage>...] STAGES]
+TIME <time_condition>
+[WHERE <criteria>]
+[GROUP BY <tag>[, <tag>...]]
+[ORDER BY <field_or_aggregation> [ASC|DESC]]
+[WITH QUERY_TRACE]
+[LIMIT <n>]
+[OFFSET <n>]
+```
+
+Aggregation functions:
+
+- `SUM(field)`
+- `MEAN(field)` or `AVG(field)`
+- `COUNT(field)`
+- `MAX(field)`
+- `MIN(field)`
+
+Measure-specific guidance:
+
+- Use `GROUP BY` when the user asks for "by service", "per instance", "grouped
by region", etc.
+- Use `identifier::TAG` or `identifier::FIELD` when a key name is ambiguous.
+- If the user asks for a raw metric value and does not ask for aggregation,
use `SELECT *` or specific fields.
+- If the user asks for average, sum, max, min, or count, use an aggregation
projection and add `GROUP BY` only when a grouping dimension is requested.
+
+Examples:
+
+```sql
+SELECT service, MEAN(response_time)
+FROM MEASURE http_metrics IN metricsMinute
+TIME > '-30m'
+WHERE region = 'us-west'
+GROUP BY service
+```
+
+```sql
+SELECT status::TAG, status::FIELD
+FROM MEASURE http_requests IN default
+TIME > '-30m'
+WHERE path = '/api/v1/users'
+```
+
+## Top-N
+
+Use `SHOW TOP` for optimized measure ranking.
+
+```sql
+SHOW TOP <n>
+FROM MEASURE <measure_name> IN <group>[, <group>...]
+[ON <stage>[, <stage>...] STAGES]
+TIME <time_condition>
+[WHERE <simple_conditions>]
+[AGGREGATE BY SUM|MEAN|AVG|COUNT|MAX|MIN]
+[ORDER BY ASC|DESC]
+[WITH QUERY_TRACE]
+```
+
+Examples:
+
+```sql
+SHOW TOP 10
+FROM MEASURE service_latency IN production
+TIME > '-30m'
+WHERE http_method = 'GET'
+AGGREGATE BY MEAN
+ORDER BY DESC
+```
+
+```sql
+SHOW TOP 5
+FROM MEASURE endpoint_errors IN production
+TIME BETWEEN '-24h' AND 'now'
+WHERE status_code = '500'
+AGGREGATE BY SUM
+ORDER BY DESC
+```
+
+Top-N generation rules:
+
+- "top N", "highest N", "largest N" -> `SHOW TOP N ... ORDER BY DESC`.
+- "bottom N", "lowest N", "smallest N" -> `SHOW TOP N ... ORDER BY ASC`.
+- Do not add `LIMIT` to `SHOW TOP`; the `TOP <n>` value is the result count.
+
+## Properties
+
+Properties query metadata/key-value records. They require group/resource names
but do not require `TIME`.
+
+```sql
+SELECT <projection>
+FROM PROPERTY <property_name> IN <group>[, <group>...]
+[WHERE <criteria>]
+[ORDER BY <field> [ASC|DESC] | ORDER BY ASC|DESC]
+[WITH QUERY_TRACE]
+[LIMIT <n>]
+[OFFSET <n>]
+```
+
+Property criteria commonly use `ID`:
+
+```sql
+SELECT *
+FROM PROPERTY server_metadata IN datacenter-1
+WHERE ID = 'server-1a2b3c'
+```
+
+```sql
+SELECT ip, region
+FROM PROPERTY server_metadata IN datacenter-1
+WHERE ID IN ('server-1', 'server-2')
+LIMIT 10
+```
+
+## Traces
+
+Traces query trace/span data. Use `SELECT ()` when the user wants trace
records without projecting tags.
+
+```sql
+SELECT <projection>|()
+FROM TRACE <trace_name> IN <group>[, <group>...]
+[ON <stage>[, <stage>...] STAGES]
+TIME <time_condition>
+[WHERE <criteria>]
+[ORDER BY <field_or_TIME> [ASC|DESC] | ORDER BY ASC|DESC]
+[WITH QUERY_TRACE]
+[LIMIT <n>]
+[OFFSET <n>]
+```
+
+Examples:
+
+```sql
+SELECT ()
+FROM TRACE sw_trace IN default
+TIME > '-30m'
+WHERE status = 'error'
+LIMIT 50
+```
+
+```sql
+SELECT trace_id, operation_name
+FROM TRACE zipkin_span IN default
+TIME > '-30m'
+WHERE operation_name MATCH(('GET', 'POST'), 'keyword')
+ORDER BY timestamp_millis DESC
+LIMIT 30
+```
+
+## Lifecycle Stages
+
+Use lifecycle stage filtering when the user asks to query warm/cold/warn
stages or explicitly names stages.
+
+```sql
+FROM STREAM sw IN default ON warn, cold STAGES
+FROM MEASURE service_cpm IN metricsMinute ON (warn, cold) STAGES
+FROM TRACE sw_trace IN default ON warn STAGES
+SHOW TOP 10 FROM MEASURE service_latency IN production ON warn, cold STAGES
TIME > '-30m'
+```
+
+## Query Trace
+
+Append `WITH QUERY_TRACE` when the user asks for query execution debug/tracing
details.
+
+```sql
+SELECT *
+FROM STREAM logs IN default
+TIME > '-30m'
+WITH QUERY_TRACE
+```
+
+## ORDER BY and LIMIT
+
+- Use `ORDER BY` only when the user asks for sorted, latest, earliest,
highest, lowest, top, bottom, first, or last results.
+- For "latest N" or "last N records", use a timestamp-like indexed field when
known, for example `ORDER BY timestamp_millis DESC LIMIT N`.
+- If no sortable field is known, prefer discovering schema/index information
or omit `ORDER BY` rather than inventing a field.
+- `LIMIT` is for result count, not time range.
+- `OFFSET` is for pagination.
+- For `SHOW TOP`, do not use `LIMIT`.
+
+## Natural-Language Mapping Checklist
+
+Before finalizing a query:
+
+1. Select the resource form: logs/events -> `STREAM`; metrics/statistics ->
`MEASURE`; trace records/spans -> `TRACE`; metadata/config/key-value ->
`PROPERTY`; ranking over metrics -> `SHOW TOP`.
+2. Determine group and resource name. If missing or ambiguous, call
`list_groups_schemas`.
+3. Add `TIME` for stream/measure/trace/topn queries.
+4. Add filters from user constraints as `WHERE`.
+5. Add aggregation/grouping only when requested by words like average, sum,
count, max, min, per, by, grouped by.
+6. Add ordering and limit only when result count or sort direction is
requested.
+7. Validate with `validate_bydbql` before execution.