From c21943e40ba8867b4402f23774c0024c4166cbfd Mon Sep 17 00:00:00 2001 From: Parfii-bot Date: Wed, 22 Apr 2026 12:45:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(ts-packages):=206=20TS=20packages=20?= =?UTF-8?q?=E2=80=94=20MCP=20server=20+=205=20external-API=20adapters?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Total 1465 LOC + 616 test LOC, 78/78 tests pass. - @keisei/mcp-server (25 tests) — Rust-CLI bridge via execa, stdio+HTTP, HMAC auth, kei() meta-tool - @keisei/telegram-adapter (16 tests) — grammy Bot, 7 tools - @keisei/recall-adapter (8 tests) — Zoom via Recall.ai, 5 tools - @keisei/grok-adapter (6 tests) — xAI OpenAI-compatible, 2 tools - @keisei/gmail-adapter (11 tests) — googleapis OAuth2, 6 tools (new — LBM gap) - @keisei/youtube-adapter (12 tests) — YouTube Data API v3, 5 tools (new — LBM gap) RULE 0.2 exception #4 (TS for MCP/API layer documented in _ts_packages/README.md). RULE 0.8 — env vars only (TELEGRAM_BOT_TOKEN, XAI_API_KEY, GMAIL_*, YOUTUBE_API_KEY). Strict TypeScript: strict + exactOptionalPropertyTypes + noUncheckedIndexedAccess. Genesis-scan clean (0 hits). --- _ts_packages/.gitignore | 5 + _ts_packages/README.md | 111 + _ts_packages/package-lock.json | 3776 +++++++++++++++++ _ts_packages/package.json | 24 + .../packages/gmail-adapter/package.json | 33 + .../packages/gmail-adapter/src/client.ts | 101 + .../packages/gmail-adapter/src/index.ts | 22 + .../packages/gmail-adapter/src/tools.ts | 93 + .../packages/gmail-adapter/src/types.ts | 40 + .../gmail-adapter/test/client.test.ts | 44 + .../packages/gmail-adapter/test/tools.test.ts | 43 + .../packages/gmail-adapter/test/types.test.ts | 29 + .../packages/gmail-adapter/tsconfig.json | 10 + .../packages/gmail-adapter/vitest.config.ts | 8 + .../packages/grok-adapter/package.json | 32 + .../packages/grok-adapter/src/client.ts | 78 + .../packages/grok-adapter/src/index.ts | 19 + .../packages/grok-adapter/src/tools.ts | 40 + .../packages/grok-adapter/test/client.test.ts | 31 + .../packages/grok-adapter/test/tools.test.ts | 36 + .../packages/grok-adapter/tsconfig.json | 10 + .../packages/grok-adapter/vitest.config.ts | 8 + _ts_packages/packages/mcp-server/package.json | 38 + .../packages/mcp-server/src/adapters.ts | 53 + .../packages/mcp-server/src/errors.ts | 61 + _ts_packages/packages/mcp-server/src/index.ts | 123 + .../packages/mcp-server/src/rust-bridge.ts | 83 + .../packages/mcp-server/src/server.ts | 91 + .../packages/mcp-server/src/tool-registry.ts | 88 + .../packages/mcp-server/test/errors.test.ts | 54 + .../mcp-server/test/kei-routing.test.ts | 26 + .../mcp-server/test/rust-bridge.test.ts | 39 + .../mcp-server/test/server-auth.test.ts | 30 + .../mcp-server/test/server-handshake.test.ts | 21 + .../mcp-server/test/tool-registry.test.ts | 31 + .../packages/mcp-server/tsconfig.json | 10 + .../packages/mcp-server/vitest.config.ts | 8 + .../packages/recall-adapter/package.json | 32 + .../packages/recall-adapter/src/client.ts | 63 + .../packages/recall-adapter/src/index.ts | 19 + .../packages/recall-adapter/src/tools.ts | 69 + .../recall-adapter/test/client.test.ts | 40 + .../recall-adapter/test/tools.test.ts | 47 + .../packages/recall-adapter/tsconfig.json | 10 + .../packages/recall-adapter/vitest.config.ts | 8 + .../packages/telegram-adapter/package.json | 33 + .../packages/telegram-adapter/src/client.ts | 74 + .../packages/telegram-adapter/src/index.ts | 23 + .../packages/telegram-adapter/src/tools.ts | 112 + .../packages/telegram-adapter/src/types.ts | 51 + .../telegram-adapter/test/client.test.ts | 26 + .../telegram-adapter/test/tools.test.ts | 44 + .../telegram-adapter/test/types.test.ts | 40 + .../packages/telegram-adapter/tsconfig.json | 10 + .../telegram-adapter/vitest.config.ts | 8 + .../packages/youtube-adapter/package.json | 34 + .../packages/youtube-adapter/src/client.ts | 112 + .../packages/youtube-adapter/src/index.ts | 20 + .../packages/youtube-adapter/src/tools.ts | 87 + .../packages/youtube-adapter/src/types.ts | 46 + .../youtube-adapter/test/client.test.ts | 37 + .../youtube-adapter/test/tools.test.ts | 55 + .../youtube-adapter/test/types.test.ts | 29 + .../packages/youtube-adapter/tsconfig.json | 10 + .../packages/youtube-adapter/vitest.config.ts | 8 + _ts_packages/tsconfig.base.json | 22 + 66 files changed, 6518 insertions(+) create mode 100644 _ts_packages/.gitignore create mode 100644 _ts_packages/README.md create mode 100644 _ts_packages/package-lock.json create mode 100644 _ts_packages/package.json create mode 100644 _ts_packages/packages/gmail-adapter/package.json create mode 100644 _ts_packages/packages/gmail-adapter/src/client.ts create mode 100644 _ts_packages/packages/gmail-adapter/src/index.ts create mode 100644 _ts_packages/packages/gmail-adapter/src/tools.ts create mode 100644 _ts_packages/packages/gmail-adapter/src/types.ts create mode 100644 _ts_packages/packages/gmail-adapter/test/client.test.ts create mode 100644 _ts_packages/packages/gmail-adapter/test/tools.test.ts create mode 100644 _ts_packages/packages/gmail-adapter/test/types.test.ts create mode 100644 _ts_packages/packages/gmail-adapter/tsconfig.json create mode 100644 _ts_packages/packages/gmail-adapter/vitest.config.ts create mode 100644 _ts_packages/packages/grok-adapter/package.json create mode 100644 _ts_packages/packages/grok-adapter/src/client.ts create mode 100644 _ts_packages/packages/grok-adapter/src/index.ts create mode 100644 _ts_packages/packages/grok-adapter/src/tools.ts create mode 100644 _ts_packages/packages/grok-adapter/test/client.test.ts create mode 100644 _ts_packages/packages/grok-adapter/test/tools.test.ts create mode 100644 _ts_packages/packages/grok-adapter/tsconfig.json create mode 100644 _ts_packages/packages/grok-adapter/vitest.config.ts create mode 100644 _ts_packages/packages/mcp-server/package.json create mode 100644 _ts_packages/packages/mcp-server/src/adapters.ts create mode 100644 _ts_packages/packages/mcp-server/src/errors.ts create mode 100644 _ts_packages/packages/mcp-server/src/index.ts create mode 100644 _ts_packages/packages/mcp-server/src/rust-bridge.ts create mode 100644 _ts_packages/packages/mcp-server/src/server.ts create mode 100644 _ts_packages/packages/mcp-server/src/tool-registry.ts create mode 100644 _ts_packages/packages/mcp-server/test/errors.test.ts create mode 100644 _ts_packages/packages/mcp-server/test/kei-routing.test.ts create mode 100644 _ts_packages/packages/mcp-server/test/rust-bridge.test.ts create mode 100644 _ts_packages/packages/mcp-server/test/server-auth.test.ts create mode 100644 _ts_packages/packages/mcp-server/test/server-handshake.test.ts create mode 100644 _ts_packages/packages/mcp-server/test/tool-registry.test.ts create mode 100644 _ts_packages/packages/mcp-server/tsconfig.json create mode 100644 _ts_packages/packages/mcp-server/vitest.config.ts create mode 100644 _ts_packages/packages/recall-adapter/package.json create mode 100644 _ts_packages/packages/recall-adapter/src/client.ts create mode 100644 _ts_packages/packages/recall-adapter/src/index.ts create mode 100644 _ts_packages/packages/recall-adapter/src/tools.ts create mode 100644 _ts_packages/packages/recall-adapter/test/client.test.ts create mode 100644 _ts_packages/packages/recall-adapter/test/tools.test.ts create mode 100644 _ts_packages/packages/recall-adapter/tsconfig.json create mode 100644 _ts_packages/packages/recall-adapter/vitest.config.ts create mode 100644 _ts_packages/packages/telegram-adapter/package.json create mode 100644 _ts_packages/packages/telegram-adapter/src/client.ts create mode 100644 _ts_packages/packages/telegram-adapter/src/index.ts create mode 100644 _ts_packages/packages/telegram-adapter/src/tools.ts create mode 100644 _ts_packages/packages/telegram-adapter/src/types.ts create mode 100644 _ts_packages/packages/telegram-adapter/test/client.test.ts create mode 100644 _ts_packages/packages/telegram-adapter/test/tools.test.ts create mode 100644 _ts_packages/packages/telegram-adapter/test/types.test.ts create mode 100644 _ts_packages/packages/telegram-adapter/tsconfig.json create mode 100644 _ts_packages/packages/telegram-adapter/vitest.config.ts create mode 100644 _ts_packages/packages/youtube-adapter/package.json create mode 100644 _ts_packages/packages/youtube-adapter/src/client.ts create mode 100644 _ts_packages/packages/youtube-adapter/src/index.ts create mode 100644 _ts_packages/packages/youtube-adapter/src/tools.ts create mode 100644 _ts_packages/packages/youtube-adapter/src/types.ts create mode 100644 _ts_packages/packages/youtube-adapter/test/client.test.ts create mode 100644 _ts_packages/packages/youtube-adapter/test/tools.test.ts create mode 100644 _ts_packages/packages/youtube-adapter/test/types.test.ts create mode 100644 _ts_packages/packages/youtube-adapter/tsconfig.json create mode 100644 _ts_packages/packages/youtube-adapter/vitest.config.ts create mode 100644 _ts_packages/tsconfig.base.json diff --git a/_ts_packages/.gitignore b/_ts_packages/.gitignore new file mode 100644 index 0000000..02191d9 --- /dev/null +++ b/_ts_packages/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +*.tsbuildinfo +.DS_Store +coverage/ diff --git a/_ts_packages/README.md b/_ts_packages/README.md new file mode 100644 index 0000000..a9d603a --- /dev/null +++ b/_ts_packages/README.md @@ -0,0 +1,111 @@ +# KeiSeiKit TypeScript Packages + +> v0.14.0 part B: MCP server layer + external-API adapters. + +## RULE 0.2 exception + +TypeScript is chosen here under **RULE 0.2 exception #4 (Browser/DOM adjacent)** because: + +1. The official Model Context Protocol SDK is TypeScript-native; Rust MCP + libraries are immature (as of 2026-04). +2. The API adapters rely on JS-native SDKs with no Rust equivalents: + - `grammy` (type-safe Telegram bot) + - `googleapis` (official Google API SDK for Gmail + YouTube) + - `youtube-transcript` (Tier-1 free transcript extractor) +3. Async, JSON-heavy glue code is TypeScript's sweet spot. + +**Core primitives (signing, ledger, graph, memory, refactor, etc.) remain +Rust** in `../_primitives/_rust/`. This TS layer is a THIN wrapper: it +spawns the Rust CLIs as subprocesses and exposes them as MCP tools, plus +the six adapters above that have no Rust equivalent. + +## Layout + +``` +_ts_packages/ +├── package.json npm workspace root +├── tsconfig.base.json strict TS 5.x +└── packages/ + ├── mcp-server/ @keisei/mcp-server + ├── telegram-adapter/ @keisei/telegram-adapter + ├── recall-adapter/ @keisei/recall-adapter (Zoom via Recall.ai) + ├── grok-adapter/ @keisei/grok-adapter (xAI) + ├── gmail-adapter/ @keisei/gmail-adapter + └── youtube-adapter/ @keisei/youtube-adapter +``` + +## Install (for end users) + +### 1. Install workspace deps + +```bash +cd _ts_packages +npm install +npm run build +``` + +### 2. Link each package as a global CLI (optional) + +```bash +npm i -g ./packages/mcp-server +npm i -g ./packages/telegram-adapter +# ... etc +``` + +Or install into a Claude agent directory: + +```bash +npm i --prefix ~/.claude/agents/_ts_packages/packages/mcp-server \ + ./_ts_packages/packages/mcp-server +``` + +## Environment variables (RULE 0.8 — secrets in `~/.claude/secrets/.env`) + +| Var | Package | Purpose | +|---|---|---| +| `TELEGRAM_BOT_TOKEN` | telegram-adapter | Bot API token | +| `RECALL_API_KEY` | recall-adapter | Recall.ai API key (Zoom meetings) | +| `XAI_API_KEY` | grok-adapter | xAI Grok API key | +| `GMAIL_CLIENT_ID` | gmail-adapter | Google OAuth2 client id | +| `GMAIL_CLIENT_SECRET` | gmail-adapter | Google OAuth2 client secret | +| `GMAIL_REFRESH_TOKEN` | gmail-adapter | Long-lived OAuth2 refresh token | +| `YOUTUBE_API_KEY` | youtube-adapter | YouTube Data API v3 key | +| `KEI_MCP_AUTH_TOKEN` | mcp-server | HMAC token for tool callers | +| `KEI_RUST_BIN_DIR` | mcp-server | Override directory holding Rust primitive CLIs | + +All are read via `process.env`. Hardcoding tokens is **forbidden** (RULE 0.8). + +## MCP server integration + +The `@keisei/mcp-server` exposes the Rust primitive CLIs as MCP tools. The +pattern is one Rust binary = one MCP tool, with the `kei` meta-tool on +top that routes natural-language queries via `kei-router`. + +Stdio mode (for Claude Code native integration): + +```bash +npx @keisei/mcp-server --stdio +``` + +HTTP mode: + +```bash +npx @keisei/mcp-server --port 3000 --auth-token-file ~/.claude/mcp-token +``` + +## Verification + +```bash +npm install +npm run build --workspaces +npm run test --workspaces +``` + +All six packages compile under `strict: true`. Total new LOC: see commit. + +## Migration notes + +- Zero impact on existing KeiSeiKit users unless they opt into the MCP + server (planned v0.14.1 installer flag `--enable-mcp`). +- The Rust primitives are unchanged; this layer only **wraps** them. +- Gmail and YouTube adapters are **new** (gaps in LBM). diff --git a/_ts_packages/package-lock.json b/_ts_packages/package-lock.json new file mode 100644 index 0000000..c767c2a --- /dev/null +++ b/_ts_packages/package-lock.json @@ -0,0 +1,3776 @@ +{ + "name": "@keisei/ts-packages", + "version": "0.14.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@keisei/ts-packages", + "version": "0.14.0", + "workspaces": [ + "packages/*" + ], + "devDependencies": { + "tsx": "^4.16.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@grammyjs/types": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@grammyjs/types/-/types-3.26.0.tgz", + "integrity": "sha512-jlnyfxfev/2o68HlvAGRocAXgdPPX5QabG7jZlbqC2r9DZyWBfzTlg+nu3O3Fy4EhgLWu28hZ/8wr7DsNamP9A==", + "license": "MIT" + }, + "node_modules/@hono/node-server": { + "version": "1.19.14", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", + "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "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/@keisei/gmail-adapter": { + "resolved": "packages/gmail-adapter", + "link": true + }, + "node_modules/@keisei/grok-adapter": { + "resolved": "packages/grok-adapter", + "link": true + }, + "node_modules/@keisei/mcp-server": { + "resolved": "packages/mcp-server", + "link": true + }, + "node_modules/@keisei/recall-adapter": { + "resolved": "packages/recall-adapter", + "link": true + }, + "node_modules/@keisei/telegram-adapter": { + "resolved": "packages/telegram-adapter", + "link": true + }, + "node_modules/@keisei/youtube-adapter": { + "resolved": "packages/youtube-adapter", + "link": true + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.2.1", + "express-rate-limit": "^8.2.1", + "hono": "^4.11.4", + "jose": "^6.1.3", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.2.tgz", + "integrity": "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.2.tgz", + "integrity": "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.2.tgz", + "integrity": "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.2.tgz", + "integrity": "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.2.tgz", + "integrity": "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.2.tgz", + "integrity": "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.2.tgz", + "integrity": "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.2.tgz", + "integrity": "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.2.tgz", + "integrity": "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.2.tgz", + "integrity": "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.2.tgz", + "integrity": "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.2.tgz", + "integrity": "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.2.tgz", + "integrity": "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.2.tgz", + "integrity": "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.2.tgz", + "integrity": "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.2.tgz", + "integrity": "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.2.tgz", + "integrity": "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.2.tgz", + "integrity": "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.2.tgz", + "integrity": "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.2.tgz", + "integrity": "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.2.tgz", + "integrity": "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.2.tgz", + "integrity": "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.2.tgz", + "integrity": "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.2.tgz", + "integrity": "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.2.tgz", + "integrity": "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "license": "MIT" + }, + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "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": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "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", + "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/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "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/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/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", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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/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/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-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.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/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/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", + "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/execa": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", + "integrity": "sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==", + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.6", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.1", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.2.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.1.1" + }, + "engines": { + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", + "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", + "license": "MIT", + "dependencies": { + "ip-address": "10.1.0" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/figures": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", + "license": "MIT", + "dependencies": { + "is-unicode-supported": "^2.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/gaxios/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gcp-metadata": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", + "dependencies": { + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", + "json-bigint": "^1.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", + "license": "MIT", + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/google-auth-library": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", + "dependencies": { + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, + "node_modules/googleapis": { + "version": "144.0.0", + "resolved": "https://registry.npmjs.org/googleapis/-/googleapis-144.0.0.tgz", + "integrity": "sha512-ELcWOXtJxjPX4vsKMh+7V+jZvgPwYMlEhQFiu2sa9Qmt5veX8nwXPksOWGGN6Zk4xCiLygUyaz7xGtcMO+Onxw==", + "license": "Apache-2.0", + "dependencies": { + "google-auth-library": "^9.0.0", + "googleapis-common": "^7.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/googleapis-common": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/googleapis-common/-/googleapis-common-7.2.0.tgz", + "integrity": "sha512-/fhDZEJZvOV3X5jmD+fKxMqma5q2Q9nZNSF3kn1F18tpxmA86BcTxAGBQdM0N89Z3bEaIs+HVznSmFJEAmMTjA==", + "license": "Apache-2.0", + "dependencies": { + "extend": "^3.0.2", + "gaxios": "^6.0.3", + "google-auth-library": "^9.7.0", + "qs": "^6.7.0", + "url-template": "^2.0.8", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/grammy": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/grammy/-/grammy-1.42.0.tgz", + "integrity": "sha512-1AdCge+AkjSdp2FwfICSFnVbl8Mq3KVHJDy+DgTI9+D6keJ0zWALPRKas5jv/8psiCzL4N2cEOcGW7O45Kn39g==", + "license": "MIT", + "dependencies": { + "@grammyjs/types": "3.26.0", + "abort-controller": "^3.0.0", + "debug": "^4.4.3", + "node-fetch": "^2.7.0" + }, + "engines": { + "node": "^12.20.0 || >=14.13.1" + } + }, + "node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", + "dependencies": { + "gaxios": "^6.0.0", + "jws": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.12.14", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.14.tgz", + "integrity": "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==", + "license": "MIT", + "engines": { + "node": ">=16.9.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/human-signals": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", + "integrity": "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-bigint": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz", + "integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.0.0" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "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", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "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/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/postcss": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.10.tgz", + "integrity": "sha512-pMMHxBOZKFU6HgAZ4eyGnwXF/EvPGGqUr0MnZ5+99485wwW41kW91A4LOGxSHhgugZmSChL5AlElNdwlNgcnLQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-ms": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.3.0.tgz", + "integrity": "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==", + "license": "MIT", + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/rollup": { + "version": "4.60.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.2.tgz", + "integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.60.2", + "@rollup/rollup-android-arm64": "4.60.2", + "@rollup/rollup-darwin-arm64": "4.60.2", + "@rollup/rollup-darwin-x64": "4.60.2", + "@rollup/rollup-freebsd-arm64": "4.60.2", + "@rollup/rollup-freebsd-x64": "4.60.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", + "@rollup/rollup-linux-arm-musleabihf": "4.60.2", + "@rollup/rollup-linux-arm64-gnu": "4.60.2", + "@rollup/rollup-linux-arm64-musl": "4.60.2", + "@rollup/rollup-linux-loong64-gnu": "4.60.2", + "@rollup/rollup-linux-loong64-musl": "4.60.2", + "@rollup/rollup-linux-ppc64-gnu": "4.60.2", + "@rollup/rollup-linux-ppc64-musl": "4.60.2", + "@rollup/rollup-linux-riscv64-gnu": "4.60.2", + "@rollup/rollup-linux-riscv64-musl": "4.60.2", + "@rollup/rollup-linux-s390x-gnu": "4.60.2", + "@rollup/rollup-linux-x64-gnu": "4.60.2", + "@rollup/rollup-linux-x64-musl": "4.60.2", + "@rollup/rollup-openbsd-x64": "4.60.2", + "@rollup/rollup-openharmony-arm64": "4.60.2", + "@rollup/rollup-win32-arm64-msvc": "4.60.2", + "@rollup/rollup-win32-ia32-msvc": "4.60.2", + "@rollup/rollup-win32-x64-gnu": "4.60.2", + "@rollup/rollup-win32-x64-msvc": "4.60.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/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/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "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-final-newline": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/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", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/url-template": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/url-template/-/url-template-2.0.8.tgz", + "integrity": "sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==", + "license": "BSD" + }, + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/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/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "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/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yoctocolors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", + "integrity": "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/youtube-transcript": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/youtube-transcript/-/youtube-transcript-1.3.0.tgz", + "integrity": "sha512-laWv9RcKIWh6rZUH3hVnOngEvtKAhFMV5UepUO6AgevPYqe2zv8KW/uCkZJDSnPwf5/AdVu0Q66/1RDblKsp6Q==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25.28 || ^4" + } + }, + "packages/gmail-adapter": { + "name": "@keisei/gmail-adapter", + "version": "0.14.0", + "dependencies": { + "googleapis": "^144.0.0", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/grok-adapter": { + "name": "@keisei/grok-adapter", + "version": "0.14.0", + "dependencies": { + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/mcp-server": { + "name": "@keisei/mcp-server", + "version": "0.14.0", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "execa": "^9.0.0", + "zod": "^3.23.0" + }, + "bin": { + "keisei-mcp-server": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/recall-adapter": { + "name": "@keisei/recall-adapter", + "version": "0.14.0", + "dependencies": { + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/telegram-adapter": { + "name": "@keisei/telegram-adapter", + "version": "0.14.0", + "dependencies": { + "grammy": "^1.28.0", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/youtube-adapter": { + "name": "@keisei/youtube-adapter", + "version": "0.14.0", + "dependencies": { + "googleapis": "^144.0.0", + "youtube-transcript": "^1.2.1", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + } + } +} diff --git a/_ts_packages/package.json b/_ts_packages/package.json new file mode 100644 index 0000000..848e27d --- /dev/null +++ b/_ts_packages/package.json @@ -0,0 +1,24 @@ +{ + "name": "@keisei/ts-packages", + "private": true, + "version": "0.14.0", + "description": "KeiSeiKit TypeScript layer — MCP server and external-API adapters", + "type": "module", + "workspaces": [ + "packages/*" + ], + "scripts": { + "build": "npm run build --workspaces --if-present", + "test": "npm run test --workspaces --if-present", + "lint": "npm run lint --workspaces --if-present", + "clean": "rm -rf packages/*/dist packages/*/*.tsbuildinfo" + }, + "engines": { + "node": ">=18.0.0" + }, + "devDependencies": { + "typescript": "^5.5.0", + "vitest": "^2.0.0", + "tsx": "^4.16.0" + } +} diff --git a/_ts_packages/packages/gmail-adapter/package.json b/_ts_packages/packages/gmail-adapter/package.json new file mode 100644 index 0000000..0783137 --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/package.json @@ -0,0 +1,33 @@ +{ + "name": "@keisei/gmail-adapter", + "version": "0.14.0", + "description": "Gmail API adapter for the KeiSei MCP server", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -b", + "test": "vitest run" + }, + "dependencies": { + "googleapis": "^144.0.0", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/_ts_packages/packages/gmail-adapter/src/client.ts b/_ts_packages/packages/gmail-adapter/src/client.ts new file mode 100644 index 0000000..34dd11d --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/src/client.ts @@ -0,0 +1,101 @@ +// Gmail API client via googleapis. One class that owns an OAuth2 client +// plus a gmail.users surface. All methods return plain data (no gapi types +// leak outward). Tests inject a mock surface via the `gmailSurface` param. + +import { google } from "googleapis"; +import type { MessageSummary } from "./types.js"; + +export interface GmailClientConfig { + clientId: string; + clientSecret: string; + refreshToken: string; + gmailSurface?: GmailSurface; +} + +// Narrow shape we actually use. Everything googleapis exposes is optional. +export interface GmailSurface { + list: (q: string | undefined, max: number) => Promise>; + get: (id: string) => Promise<{ id?: string | null; threadId?: string | null; snippet?: string | null; payload?: { headers?: Array<{ name?: string | null; value?: string | null }> | null } | null }>; + modify: (id: string, addIds: string[], removeIds: string[]) => Promise; + trash: (id: string) => Promise; +} + +export class GmailClient { + private readonly surface: GmailSurface; + + constructor(cfg: GmailClientConfig) { + this.surface = cfg.gmailSurface ?? buildDefaultSurface(cfg); + } + + async listUnread(max: number): Promise { + const ids = await this.surface.list("is:unread", max); + return Promise.all(ids.map(async (row) => this.summarize(row.id ?? ""))); + } + + async search(query: string, max: number): Promise { + const ids = await this.surface.list(query, max); + return Promise.all(ids.map(async (row) => this.summarize(row.id ?? ""))); + } + + async getMessage(id: string): Promise { + return this.summarize(id); + } + + async labelMessage(id: string, label: string): Promise { + await this.surface.modify(id, [label], []); + } + + async archive(id: string): Promise { + await this.surface.modify(id, [], ["INBOX"]); + } + + async trash(id: string): Promise { + await this.surface.trash(id); + } + + private async summarize(id: string): Promise { + if (!id) return { id: "" }; + const msg = await this.surface.get(id); + const headers = msg.payload?.headers ?? []; + const pick = (name: string): string | undefined => headers.find((h) => h.name?.toLowerCase() === name)?.value ?? undefined; + return { + id: msg.id ?? id, + threadId: msg.threadId ?? undefined, + subject: pick("subject"), + from: pick("from"), + date: pick("date"), + snippet: msg.snippet ?? undefined, + }; + } +} + +function buildDefaultSurface(cfg: GmailClientConfig): GmailSurface { + if (!cfg.clientId || !cfg.clientSecret || !cfg.refreshToken) { + throw new Error("GMAIL_CLIENT_ID, GMAIL_CLIENT_SECRET, GMAIL_REFRESH_TOKEN all required"); + } + const oauth = new google.auth.OAuth2(cfg.clientId, cfg.clientSecret); + oauth.setCredentials({ refresh_token: cfg.refreshToken }); + const gmail = google.gmail({ version: "v1", auth: oauth }); + return { + list: async (q, max) => { + const params: { userId: string; maxResults: number; q?: string } = { userId: "me", maxResults: max }; + if (q !== undefined) params.q = q; + const res = await gmail.users.messages.list(params); + const items = res.data.messages ?? []; + return items.map((m: { id?: string | null; threadId?: string | null }) => ({ + id: m.id ?? null, + threadId: m.threadId ?? null, + })); + }, + get: async (id) => { + const res = await gmail.users.messages.get({ userId: "me", id, format: "metadata" }); + return res.data; + }, + modify: async (id, addIds, removeIds) => { + await gmail.users.messages.modify({ userId: "me", id, requestBody: { addLabelIds: addIds, removeLabelIds: removeIds } }); + }, + trash: async (id) => { + await gmail.users.messages.trash({ userId: "me", id }); + }, + }; +} diff --git a/_ts_packages/packages/gmail-adapter/src/index.ts b/_ts_packages/packages/gmail-adapter/src/index.ts new file mode 100644 index 0000000..9ce55b2 --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/src/index.ts @@ -0,0 +1,22 @@ +import { GmailClient } from "./client.js"; +import { buildGmailTools, type GmailTool } from "./tools.js"; + +export { GmailClient } from "./client.js"; +export { buildGmailTools } from "./tools.js"; +export type { GmailTool } from "./tools.js"; +export * from "./types.js"; + +type Registrar = (tool: GmailTool) => void; + +export function registerAdapter(register: Registrar): void { + const clientId = process.env["GMAIL_CLIENT_ID"]; + const clientSecret = process.env["GMAIL_CLIENT_SECRET"]; + const refreshToken = process.env["GMAIL_REFRESH_TOKEN"]; + if (!clientId || !clientSecret || !refreshToken) { + throw new Error( + "GMAIL_{CLIENT_ID,CLIENT_SECRET,REFRESH_TOKEN} env vars required; see ~/.claude/secrets/.env (RULE 0.8).", + ); + } + const client = new GmailClient({ clientId, clientSecret, refreshToken }); + for (const tool of buildGmailTools(client)) register(tool); +} diff --git a/_ts_packages/packages/gmail-adapter/src/tools.ts b/_ts_packages/packages/gmail-adapter/src/tools.ts new file mode 100644 index 0000000..1e56be4 --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/src/tools.ts @@ -0,0 +1,93 @@ +import { z } from "zod"; +import { GmailClient } from "./client.js"; +import { + GetMessageArgs, + LabelArgs, + ListUnreadArgs, + ModifyOnlyArgs, + SearchArgs, + type MessageSummary, +} from "./types.js"; + +export interface GmailTool { + name: string; + description: string; + inputSchema: z.ZodObject>; + handler: (args: Record) => Promise; +} + +export function buildGmailTools(client: GmailClient): GmailTool[] { + return [ + { + name: "gmail_list_unread", + description: "List unread messages (up to 500).", + inputSchema: ListUnreadArgs, + handler: async (raw) => { + const args = ListUnreadArgs.parse(raw); + return formatList(await client.listUnread(args.max)); + }, + }, + { + name: "gmail_get_message", + description: "Fetch one message by id; returns headers + snippet.", + inputSchema: GetMessageArgs, + handler: async (raw) => { + const args = GetMessageArgs.parse(raw); + return formatOne(await client.getMessage(args.id)); + }, + }, + { + name: "gmail_search", + description: "Search mailbox using Gmail operators (e.g. 'from:alice has:attachment').", + inputSchema: SearchArgs, + handler: async (raw) => { + const args = SearchArgs.parse(raw); + return formatList(await client.search(args.query, args.max)); + }, + }, + { + name: "gmail_label_message", + description: "Apply a Gmail label id to a message.", + inputSchema: LabelArgs, + handler: async (raw) => { + const args = LabelArgs.parse(raw); + await client.labelMessage(args.id, args.label); + return `labeled ${args.id} with ${args.label}`; + }, + }, + { + name: "gmail_archive", + description: "Archive a message (removes INBOX label).", + inputSchema: ModifyOnlyArgs, + handler: async (raw) => { + const args = ModifyOnlyArgs.parse(raw); + await client.archive(args.id); + return `archived ${args.id}`; + }, + }, + { + name: "gmail_trash", + description: "Move a message to Trash.", + inputSchema: ModifyOnlyArgs, + handler: async (raw) => { + const args = ModifyOnlyArgs.parse(raw); + await client.trash(args.id); + return `trashed ${args.id}`; + }, + }, + ]; +} + +function formatList(msgs: MessageSummary[]): string { + if (msgs.length === 0) return "No messages."; + return msgs.map(formatOne).join("\n---\n"); +} + +function formatOne(m: MessageSummary): string { + const parts = [`id: ${m.id}`]; + if (m.subject) parts.push(`subject: ${m.subject}`); + if (m.from) parts.push(`from: ${m.from}`); + if (m.date) parts.push(`date: ${m.date}`); + if (m.snippet) parts.push(`snippet: ${m.snippet}`); + return parts.join("\n"); +} diff --git a/_ts_packages/packages/gmail-adapter/src/types.ts b/_ts_packages/packages/gmail-adapter/src/types.ts new file mode 100644 index 0000000..ad3a57b --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/src/types.ts @@ -0,0 +1,40 @@ +// Gmail API tool I/O types. Types live in their own file so tests can +// exercise schemas without importing googleapis. + +import { z } from "zod"; + +export const ListUnreadArgs = z.object({ + max: z.number().int().positive().max(500).default(20), +}); +export type ListUnreadArgs = z.infer; + +export const GetMessageArgs = z.object({ + id: z.string().min(1), +}); +export type GetMessageArgs = z.infer; + +export const SearchArgs = z.object({ + query: z.string().min(1), + max: z.number().int().positive().max(500).default(20), +}); +export type SearchArgs = z.infer; + +export const LabelArgs = z.object({ + id: z.string().min(1), + label: z.string().min(1), +}); +export type LabelArgs = z.infer; + +export const ModifyOnlyArgs = z.object({ + id: z.string().min(1), +}); +export type ModifyOnlyArgs = z.infer; + +export interface MessageSummary { + id: string; + threadId?: string | undefined; + subject?: string | undefined; + from?: string | undefined; + snippet?: string | undefined; + date?: string | undefined; +} diff --git a/_ts_packages/packages/gmail-adapter/test/client.test.ts b/_ts_packages/packages/gmail-adapter/test/client.test.ts new file mode 100644 index 0000000..35c5006 --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/test/client.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect, vi } from "vitest"; +import { GmailClient, type GmailSurface } from "../src/client.js"; + +function makeSurface(): GmailSurface { + return { + list: vi.fn(async () => [{ id: "m1", threadId: "t1" }, { id: "m2" }]), + get: vi.fn(async (id: string) => ({ + id, + snippet: `snip-${id}`, + payload: { + headers: [ + { name: "Subject", value: `subj-${id}` }, + { name: "From", value: "alice@example.com" }, + ], + }, + })), + modify: vi.fn(async () => undefined), + trash: vi.fn(async () => undefined), + }; +} + +describe("GmailClient", () => { + it("listUnread returns summarized messages", async () => { + const surface = makeSurface(); + const c = new GmailClient({ clientId: "", clientSecret: "", refreshToken: "", gmailSurface: surface }); + const out = await c.listUnread(10); + expect(out).toHaveLength(2); + expect(out[0]?.subject).toBe("subj-m1"); + }); + + it("labelMessage calls modify with addIds only", async () => { + const surface = makeSurface(); + const c = new GmailClient({ clientId: "", clientSecret: "", refreshToken: "", gmailSurface: surface }); + await c.labelMessage("m1", "IMPORTANT"); + expect(surface.modify).toHaveBeenCalledWith("m1", ["IMPORTANT"], []); + }); + + it("archive removes INBOX label", async () => { + const surface = makeSurface(); + const c = new GmailClient({ clientId: "", clientSecret: "", refreshToken: "", gmailSurface: surface }); + await c.archive("m1"); + expect(surface.modify).toHaveBeenCalledWith("m1", [], ["INBOX"]); + }); +}); diff --git a/_ts_packages/packages/gmail-adapter/test/tools.test.ts b/_ts_packages/packages/gmail-adapter/test/tools.test.ts new file mode 100644 index 0000000..8b2d56a --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/test/tools.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect, vi } from "vitest"; +import { GmailClient, type GmailSurface } from "../src/client.js"; +import { buildGmailTools } from "../src/tools.js"; + +function mkSurface(): GmailSurface { + return { + list: vi.fn(async () => [{ id: "m1" }]), + get: vi.fn(async () => ({ id: "m1", snippet: "hello", payload: { headers: [] } })), + modify: vi.fn(async () => undefined), + trash: vi.fn(async () => undefined), + }; +} + +describe("gmail tool surface", () => { + it("registers 6 tools", () => { + const c = new GmailClient({ clientId: "", clientSecret: "", refreshToken: "", gmailSurface: mkSurface() }); + const names = buildGmailTools(c).map((t) => t.name); + expect(names).toEqual([ + "gmail_list_unread", + "gmail_get_message", + "gmail_search", + "gmail_label_message", + "gmail_archive", + "gmail_trash", + ]); + }); + + it("gmail_list_unread formats empty list", async () => { + const surface: GmailSurface = { ...mkSurface(), list: vi.fn(async () => []) }; + const c = new GmailClient({ clientId: "", clientSecret: "", refreshToken: "", gmailSurface: surface }); + const tool = buildGmailTools(c).find((t) => t.name === "gmail_list_unread"); + const out = await tool!.handler({}); + expect(out).toBe("No messages."); + }); + + it("gmail_trash returns ok string", async () => { + const surface = mkSurface(); + const c = new GmailClient({ clientId: "", clientSecret: "", refreshToken: "", gmailSurface: surface }); + const tool = buildGmailTools(c).find((t) => t.name === "gmail_trash"); + const out = await tool!.handler({ id: "m1" }); + expect(out).toContain("trashed m1"); + }); +}); diff --git a/_ts_packages/packages/gmail-adapter/test/types.test.ts b/_ts_packages/packages/gmail-adapter/test/types.test.ts new file mode 100644 index 0000000..b318f9d --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/test/types.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from "vitest"; +import { ListUnreadArgs, SearchArgs, LabelArgs, GetMessageArgs } from "../src/types.js"; + +describe("gmail schemas", () => { + it("ListUnreadArgs defaults max to 20", () => { + const r = ListUnreadArgs.safeParse({}); + expect(r.success).toBe(true); + if (r.success) expect(r.data.max).toBe(20); + }); + + it("ListUnreadArgs rejects max=0", () => { + const r = ListUnreadArgs.safeParse({ max: 0 }); + expect(r.success).toBe(false); + }); + + it("SearchArgs rejects empty query", () => { + const r = SearchArgs.safeParse({ query: "" }); + expect(r.success).toBe(false); + }); + + it("LabelArgs requires both id and label", () => { + expect(LabelArgs.safeParse({ id: "x" }).success).toBe(false); + expect(LabelArgs.safeParse({ id: "x", label: "L" }).success).toBe(true); + }); + + it("GetMessageArgs requires non-empty id", () => { + expect(GetMessageArgs.safeParse({ id: "" }).success).toBe(false); + }); +}); diff --git a/_ts_packages/packages/gmail-adapter/tsconfig.json b/_ts_packages/packages/gmail-adapter/tsconfig.json new file mode 100644 index 0000000..2c00eac --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "test/**/*"] +} diff --git a/_ts_packages/packages/gmail-adapter/vitest.config.ts b/_ts_packages/packages/gmail-adapter/vitest.config.ts new file mode 100644 index 0000000..d9ebefd --- /dev/null +++ b/_ts_packages/packages/gmail-adapter/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + environment: "node", + }, +}); diff --git a/_ts_packages/packages/grok-adapter/package.json b/_ts_packages/packages/grok-adapter/package.json new file mode 100644 index 0000000..cd4c278 --- /dev/null +++ b/_ts_packages/packages/grok-adapter/package.json @@ -0,0 +1,32 @@ +{ + "name": "@keisei/grok-adapter", + "version": "0.14.0", + "description": "xAI Grok adapter (deep research + image gen) for the KeiSei MCP server", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -b", + "test": "vitest run" + }, + "dependencies": { + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/_ts_packages/packages/grok-adapter/src/client.ts b/_ts_packages/packages/grok-adapter/src/client.ts new file mode 100644 index 0000000..f251723 --- /dev/null +++ b/_ts_packages/packages/grok-adapter/src/client.ts @@ -0,0 +1,78 @@ +// Minimal xAI Grok client. The public endpoints follow the OpenAI +// compatible shape: https://api.x.ai/v1/chat/completions and /images. +// Shapes verified against https://docs.x.ai/api (2026-04). + +export type FetchFn = typeof fetch; + +export interface GrokClientConfig { + apiKey: string; + baseUrl?: string; + fetchImpl?: FetchFn; + researchModel?: string; + imageModel?: string; +} + +const DEFAULT_BASE = "https://api.x.ai/v1"; + +export class GrokClient { + private readonly apiKey: string; + private readonly baseUrl: string; + private readonly fetchImpl: FetchFn; + private readonly researchModel: string; + private readonly imageModel: string; + + constructor(cfg: GrokClientConfig) { + if (!cfg.apiKey) throw new Error("XAI_API_KEY is required"); + this.apiKey = cfg.apiKey; + this.baseUrl = cfg.baseUrl ?? DEFAULT_BASE; + this.fetchImpl = cfg.fetchImpl ?? fetch; + this.researchModel = cfg.researchModel ?? "grok-4-heavy"; + this.imageModel = cfg.imageModel ?? "grok-2-image"; + } + + async deepResearch(query: string): Promise { + const body = { + model: this.researchModel, + messages: [{ role: "user", content: query }], + }; + const data = (await this.postJson("/chat/completions", body)) as ChatCompletionResponse; + const first = data.choices[0]; + return first?.message?.content ?? ""; + } + + async imageGenerate(prompt: string, pro = false): Promise { + const body = { + model: this.imageModel, + prompt, + n: 1, + quality: pro ? "pro" : "standard", + }; + const data = (await this.postJson("/images/generations", body)) as ImageResponse; + return (data.data ?? []).map((d) => d.url).filter((u): u is string => typeof u === "string"); + } + + private async postJson(path: string, body: unknown): Promise { + const res = await this.fetchImpl(`${this.baseUrl}${path}`, { + method: "POST", + headers: { + Authorization: `Bearer ${this.apiKey}`, + "Content-Type": "application/json", + Accept: "application/json", + }, + body: JSON.stringify(body), + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`grok ${path} -> ${res.status}: ${text}`); + } + return res.json(); + } +} + +interface ChatCompletionResponse { + choices: Array<{ message: { content: string } }>; +} + +interface ImageResponse { + data: Array<{ url?: string }>; +} diff --git a/_ts_packages/packages/grok-adapter/src/index.ts b/_ts_packages/packages/grok-adapter/src/index.ts new file mode 100644 index 0000000..a810425 --- /dev/null +++ b/_ts_packages/packages/grok-adapter/src/index.ts @@ -0,0 +1,19 @@ +import { GrokClient } from "./client.js"; +import { buildGrokTools, type GrokTool } from "./tools.js"; + +export { GrokClient } from "./client.js"; +export { buildGrokTools } from "./tools.js"; +export type { GrokTool } from "./tools.js"; + +type Registrar = (tool: GrokTool) => void; + +export function registerAdapter(register: Registrar): void { + const apiKey = process.env["XAI_API_KEY"]; + if (!apiKey) { + throw new Error( + "XAI_API_KEY env var is missing; set it in ~/.claude/secrets/.env (RULE 0.8).", + ); + } + const client = new GrokClient({ apiKey }); + for (const tool of buildGrokTools(client)) register(tool); +} diff --git a/_ts_packages/packages/grok-adapter/src/tools.ts b/_ts_packages/packages/grok-adapter/src/tools.ts new file mode 100644 index 0000000..004d1d7 --- /dev/null +++ b/_ts_packages/packages/grok-adapter/src/tools.ts @@ -0,0 +1,40 @@ +import { z } from "zod"; +import { GrokClient } from "./client.js"; + +export interface GrokTool { + name: string; + description: string; + inputSchema: z.ZodObject>; + handler: (args: Record) => Promise; +} + +const ResearchArgs = z.object({ query: z.string().min(1) }); +const ImagineArgs = z.object({ + prompt: z.string().min(1), + quality: z.enum(["standard", "pro"]).default("standard"), +}); + +export function buildGrokTools(client: GrokClient): GrokTool[] { + return [ + { + name: "grok_research", + description: "Deep research via Grok heavy model. Returns assistant message content.", + inputSchema: ResearchArgs, + handler: async (raw) => { + const args = ResearchArgs.parse(raw); + return client.deepResearch(args.query); + }, + }, + { + name: "grok_imagine", + description: "Generate an image from a prompt via Grok Imagine.", + inputSchema: ImagineArgs, + handler: async (raw) => { + const args = ImagineArgs.parse(raw); + const urls = await client.imageGenerate(args.prompt, args.quality === "pro"); + if (urls.length === 0) return "No image returned."; + return urls.join("\n"); + }, + }, + ]; +} diff --git a/_ts_packages/packages/grok-adapter/test/client.test.ts b/_ts_packages/packages/grok-adapter/test/client.test.ts new file mode 100644 index 0000000..dec82a3 --- /dev/null +++ b/_ts_packages/packages/grok-adapter/test/client.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect, vi } from "vitest"; +import { GrokClient } from "../src/client.js"; + +function makeFetchMock(payload: unknown, ok = true, status = 200): typeof fetch { + return vi.fn(async () => ({ + ok, + status, + async text() { return JSON.stringify(payload); }, + async json() { return payload; }, + } as unknown as Response)) as unknown as typeof fetch; +} + +describe("GrokClient", () => { + it("rejects empty API key", () => { + expect(() => new GrokClient({ apiKey: "" })).toThrow(/XAI_API_KEY/); + }); + + it("deepResearch returns assistant content", async () => { + const fetchImpl = makeFetchMock({ choices: [{ message: { content: "hello" } }] }); + const c = new GrokClient({ apiKey: "k", fetchImpl }); + const out = await c.deepResearch("q"); + expect(out).toBe("hello"); + }); + + it("imageGenerate extracts URLs from response", async () => { + const fetchImpl = makeFetchMock({ data: [{ url: "https://x/img.png" }] }); + const c = new GrokClient({ apiKey: "k", fetchImpl }); + const urls = await c.imageGenerate("a cat", true); + expect(urls).toEqual(["https://x/img.png"]); + }); +}); diff --git a/_ts_packages/packages/grok-adapter/test/tools.test.ts b/_ts_packages/packages/grok-adapter/test/tools.test.ts new file mode 100644 index 0000000..5545fe6 --- /dev/null +++ b/_ts_packages/packages/grok-adapter/test/tools.test.ts @@ -0,0 +1,36 @@ +import { describe, it, expect, vi } from "vitest"; +import { GrokClient } from "../src/client.js"; +import { buildGrokTools } from "../src/tools.js"; + +function makeFetchMock(payload: unknown): typeof fetch { + return vi.fn(async () => ({ + ok: true, + status: 200, + async text() { return JSON.stringify(payload); }, + async json() { return payload; }, + } as unknown as Response)) as unknown as typeof fetch; +} + +describe("grok tools", () => { + it("exposes 2 tools", () => { + const c = new GrokClient({ apiKey: "k", fetchImpl: makeFetchMock({}) }); + const tools = buildGrokTools(c); + expect(tools.map((t) => t.name)).toEqual(["grok_research", "grok_imagine"]); + }); + + it("grok_research validates non-empty query", async () => { + const c = new GrokClient({ apiKey: "k", fetchImpl: makeFetchMock({}) }); + const tool = buildGrokTools(c).find((t) => t.name === "grok_research"); + await expect(tool!.handler({ query: "" })).rejects.toBeTruthy(); + }); + + it("grok_imagine defaults quality to standard", async () => { + const c = new GrokClient({ + apiKey: "k", + fetchImpl: makeFetchMock({ data: [{ url: "u" }] }), + }); + const tool = buildGrokTools(c).find((t) => t.name === "grok_imagine"); + const out = await tool!.handler({ prompt: "x" }); + expect(out).toContain("u"); + }); +}); diff --git a/_ts_packages/packages/grok-adapter/tsconfig.json b/_ts_packages/packages/grok-adapter/tsconfig.json new file mode 100644 index 0000000..2c00eac --- /dev/null +++ b/_ts_packages/packages/grok-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "test/**/*"] +} diff --git a/_ts_packages/packages/grok-adapter/vitest.config.ts b/_ts_packages/packages/grok-adapter/vitest.config.ts new file mode 100644 index 0000000..d9ebefd --- /dev/null +++ b/_ts_packages/packages/grok-adapter/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + environment: "node", + }, +}); diff --git a/_ts_packages/packages/mcp-server/package.json b/_ts_packages/packages/mcp-server/package.json new file mode 100644 index 0000000..04f6adc --- /dev/null +++ b/_ts_packages/packages/mcp-server/package.json @@ -0,0 +1,38 @@ +{ + "name": "@keisei/mcp-server", + "version": "0.14.0", + "description": "MCP server exposing KeiSeiKit Rust primitives as Model Context Protocol tools", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "bin": { + "keisei-mcp-server": "./dist/index.js" + }, + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -b", + "test": "vitest run", + "dev": "tsx src/index.ts --stdio" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "^1.0.0", + "execa": "^9.0.0", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/_ts_packages/packages/mcp-server/src/adapters.ts b/_ts_packages/packages/mcp-server/src/adapters.ts new file mode 100644 index 0000000..6a8cbc0 --- /dev/null +++ b/_ts_packages/packages/mcp-server/src/adapters.ts @@ -0,0 +1,53 @@ +// Register external API adapters (Telegram, Recall, Grok, Gmail, YouTube) +// dynamically IF the sibling packages are installed in the runtime. Each +// adapter exports `registerAdapter(register)` by convention. + +import type { ToolDefinition } from "./tool-registry.js"; + +export type AdapterRegistrar = (tool: ToolDefinition) => void; + +interface AdapterModule { + registerAdapter: (register: AdapterRegistrar) => void; +} + +const ADAPTER_PACKAGES: readonly string[] = [ + "@keisei/telegram-adapter", + "@keisei/recall-adapter", + "@keisei/grok-adapter", + "@keisei/gmail-adapter", + "@keisei/youtube-adapter", +]; + +export async function loadAllAdapters( + register: AdapterRegistrar, + logger: (msg: string) => void = () => {}, +): Promise<{ loaded: string[]; skipped: string[] }> { + const loaded: string[] = []; + const skipped: string[] = []; + for (const pkg of ADAPTER_PACKAGES) { + const ok = await tryLoadOne(pkg, register, logger); + if (ok) loaded.push(pkg); + else skipped.push(pkg); + } + return { loaded, skipped }; +} + +async function tryLoadOne( + pkg: string, + register: AdapterRegistrar, + logger: (msg: string) => void, +): Promise { + try { + const mod = (await import(pkg)) as AdapterModule; + if (typeof mod.registerAdapter !== "function") { + logger(`adapter ${pkg}: missing registerAdapter()`); + return false; + } + mod.registerAdapter(register); + return true; + } catch (err) { + const msg = err instanceof Error ? err.message : String(err); + logger(`adapter ${pkg}: not installed (${msg})`); + return false; + } +} diff --git a/_ts_packages/packages/mcp-server/src/errors.ts b/_ts_packages/packages/mcp-server/src/errors.ts new file mode 100644 index 0000000..509d4ec --- /dev/null +++ b/_ts_packages/packages/mcp-server/src/errors.ts @@ -0,0 +1,61 @@ +// Typed error hierarchy for MCP server. One class per failure mode. +// Keeps the main handler branches flat and the JSON-RPC error codes consistent. + +export class McpServerError extends Error { + public readonly code: number; + public readonly data: unknown; + + constructor(message: string, code: number, data?: unknown) { + super(message); + this.name = new.target.name; + this.code = code; + this.data = data; + } +} + +export class AuthError extends McpServerError { + constructor(message = "unauthorized", data?: unknown) { + super(message, -32001, data); + } +} + +export class ToolNotFoundError extends McpServerError { + constructor(toolName: string) { + super(`tool not found: ${toolName}`, -32601, { tool: toolName }); + } +} + +export class RustBridgeError extends McpServerError { + constructor(message: string, data?: unknown) { + super(`rust bridge: ${message}`, -32002, data); + } +} + +export class SchemaError extends McpServerError { + constructor(message: string, data?: unknown) { + super(`schema: ${message}`, -32602, data); + } +} + +export class TimeoutError extends McpServerError { + constructor(toolName: string, ms: number) { + super(`tool ${toolName} timed out after ${ms}ms`, -32003, { tool: toolName, ms }); + } +} + +export function isMcpError(err: unknown): err is McpServerError { + return err instanceof McpServerError; +} + +export function toErrorPayload(err: unknown): { code: number; message: string; data?: unknown } { + if (isMcpError(err)) { + const payload: { code: number; message: string; data?: unknown } = { + code: err.code, + message: err.message, + }; + if (err.data !== undefined) payload.data = err.data; + return payload; + } + const message = err instanceof Error ? err.message : String(err); + return { code: -32000, message }; +} diff --git a/_ts_packages/packages/mcp-server/src/index.ts b/_ts_packages/packages/mcp-server/src/index.ts new file mode 100644 index 0000000..22dbb58 --- /dev/null +++ b/_ts_packages/packages/mcp-server/src/index.ts @@ -0,0 +1,123 @@ +#!/usr/bin/env node +// Entry point: parse argv, select transport (stdio or HTTP), start McpServer. + +import fs from "node:fs/promises"; +import path from "node:path"; +import { McpServer } from "./server.js"; + +interface CliArgs { + stdio: boolean; + port?: number; + authTokenFile?: string; + rustBinDir: string; +} + +function parseArgv(argv: readonly string[]): CliArgs { + const out: CliArgs = { + stdio: false, + rustBinDir: process.env["KEI_RUST_BIN_DIR"] ?? defaultBinDir(), + }; + for (let i = 0; i < argv.length; i++) { + const a = argv[i]; + if (a === "--stdio") out.stdio = true; + else if (a === "--port") out.port = Number(argv[++i] ?? ""); + else if (a === "--auth-token-file") { + const v = argv[++i]; + if (v !== undefined) out.authTokenFile = v; + } else if (a === "--rust-bin-dir") { + const v = argv[++i]; + if (v !== undefined) out.rustBinDir = v; + } + } + return out; +} + +function defaultBinDir(): string { + const home = process.env["HOME"] ?? ""; + return path.join(home, ".claude", "agents", "_primitives", "_rust", "target", "release"); +} + +async function readTokenFile(p: string | undefined): Promise { + if (!p) return process.env["KEI_MCP_AUTH_TOKEN"]; + const raw = await fs.readFile(p, "utf8"); + return raw.trim(); +} + +async function main(): Promise { + const args = parseArgv(process.argv.slice(2)); + const token = args.stdio ? undefined : await readTokenFile(args.authTokenFile); + const server = new McpServer({ + rustBinDir: args.rustBinDir, + ...(token !== undefined ? { authToken: token } : {}), + }); + await server.loadAdapters((m) => process.stderr.write(`[adapters] ${m}\n`)); + if (args.stdio) await runStdio(server); + else await runHttp(server, args.port ?? 3000); +} + +async function runStdio(server: McpServer): Promise { + process.stderr.write(`[keisei-mcp] stdio mode; ${server.listTools().length} tools\n`); + process.stdin.setEncoding("utf8"); + for await (const chunk of process.stdin) { + for (const line of String(chunk).split("\n")) { + const trimmed = line.trim(); + if (!trimmed) continue; + const resp = await dispatchStdioLine(server, trimmed); + process.stdout.write(resp + "\n"); + } + } +} + +async function dispatchStdioLine(server: McpServer, line: string): Promise { + try { + const payload = JSON.parse(line) as { tool: string; args?: Record }; + const call = payload.args !== undefined + ? { tool: payload.tool, args: payload.args } + : { tool: payload.tool }; + const resp = await server.handle(call); + return JSON.stringify(resp); + } catch (err) { + return JSON.stringify({ ok: false, error: { code: -32700, message: String(err) } }); + } +} + +async function runHttp(server: McpServer, port: number): Promise { + const http = await import("node:http"); + const srv = http.createServer((req, res) => void handleHttp(server, req, res)); + srv.listen(port, () => + process.stderr.write(`[keisei-mcp] http :${port}; ${server.listTools().length} tools\n`), + ); +} + +async function handleHttp(server: McpServer, req: import("node:http").IncomingMessage, res: import("node:http").ServerResponse): Promise { + if (req.method !== "POST") { + res.writeHead(405); + res.end(); + return; + } + const chunks: Buffer[] = []; + for await (const c of req) chunks.push(c as Buffer); + try { + const body = JSON.parse(Buffer.concat(chunks).toString("utf8")) as { + tool: string; + args?: Record; + }; + const authHeader = req.headers["authorization"]; + const header = typeof authHeader === "string" ? authHeader.replace(/^Bearer\s+/i, "") : undefined; + const resp = await server.handle({ + tool: body.tool, + ...(body.args !== undefined ? { args: body.args } : {}), + ...(header !== undefined ? { authHeader: header } : {}), + }); + res.writeHead(resp.ok ? 200 : 400, { "content-type": "application/json" }); + res.end(JSON.stringify(resp)); + } catch (err) { + res.writeHead(400, { "content-type": "application/json" }); + res.end(JSON.stringify({ ok: false, error: { code: -32700, message: String(err) } })); + } +} + +main().catch((err: unknown) => { + process.stderr.write(`[keisei-mcp] fatal: ${String(err)}\n`); + process.exit(1); +}); diff --git a/_ts_packages/packages/mcp-server/src/rust-bridge.ts b/_ts_packages/packages/mcp-server/src/rust-bridge.ts new file mode 100644 index 0000000..0d8111b --- /dev/null +++ b/_ts_packages/packages/mcp-server/src/rust-bridge.ts @@ -0,0 +1,83 @@ +// Bridge layer: spawn Rust primitive CLIs and marshal JSON args <-> CLI flags. +// One Rust binary = one MCP tool. Subprocess lifecycle is isolated per call. + +import { execa } from "execa"; +import path from "node:path"; +import { RustBridgeError, TimeoutError } from "./errors.js"; + +const DEFAULT_TIMEOUT_MS = 30_000; + +export interface RustCallRequest { + binary: string; + args: readonly string[]; + stdin?: string; + timeoutMs?: number; +} + +export interface RustCallResult { + stdout: string; + stderr: string; + exitCode: number; +} + +export interface RustBridgeConfig { + binDir: string; + defaultTimeoutMs?: number; +} + +export class RustBridge { + private readonly binDir: string; + private readonly defaultTimeoutMs: number; + + constructor(cfg: RustBridgeConfig) { + this.binDir = cfg.binDir; + this.defaultTimeoutMs = cfg.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS; + } + + async call(req: RustCallRequest): Promise { + const binPath = this.resolveBin(req.binary); + const timeoutMs = req.timeoutMs ?? this.defaultTimeoutMs; + try { + const opts = { + timeout: timeoutMs, + reject: false as const, + env: process.env, + ...(req.stdin !== undefined ? { input: req.stdin } : {}), + }; + const child = execa(binPath, [...req.args], opts); + const result = await child; + if (result.timedOut) throw new TimeoutError(req.binary, timeoutMs); + return { + stdout: typeof result.stdout === "string" ? result.stdout : "", + stderr: typeof result.stderr === "string" ? result.stderr : "", + exitCode: result.exitCode ?? -1, + }; + } catch (err) { + if (err instanceof TimeoutError) throw err; + const msg = err instanceof Error ? err.message : String(err); + throw new RustBridgeError(msg, { binary: req.binary }); + } + } + + private resolveBin(binary: string): string { + if (!/^[a-z0-9][a-z0-9_-]*$/i.test(binary)) { + throw new RustBridgeError(`invalid binary name: ${binary}`); + } + return path.join(this.binDir, binary); + } +} + +// Convert a JSON object of named args to CLI flags: {foo_bar: "v"} => ["--foo-bar", "v"] +export function jsonArgsToCli(args: Record): string[] { + const out: string[] = []; + for (const [key, raw] of Object.entries(args)) { + if (raw === undefined || raw === null) continue; + const flag = `--${key.replace(/_/g, "-")}`; + if (typeof raw === "boolean") { + if (raw) out.push(flag); + continue; + } + out.push(flag, String(raw)); + } + return out; +} diff --git a/_ts_packages/packages/mcp-server/src/server.ts b/_ts_packages/packages/mcp-server/src/server.ts new file mode 100644 index 0000000..eba0805 --- /dev/null +++ b/_ts_packages/packages/mcp-server/src/server.ts @@ -0,0 +1,91 @@ +// MCP server assembly: wire registry + adapters + auth into a JSON-RPC dispatcher. +// Transport-agnostic; index.ts chooses stdio or HTTP. + +import crypto from "node:crypto"; +import { z } from "zod"; +import { buildRegistry, lookupTool, type ToolDefinition } from "./tool-registry.js"; +import { RustBridge } from "./rust-bridge.js"; +import { loadAllAdapters } from "./adapters.js"; +import { AuthError, SchemaError, toErrorPayload } from "./errors.js"; + +export interface ServerConfig { + rustBinDir: string; + authToken?: string; + timeoutMs?: number; +} + +export interface JsonRpcCall { + tool: string; + args?: Record; + authHeader?: string | undefined; +} + +export interface JsonRpcResponse { + ok: boolean; + result?: string; + error?: { code: number; message: string; data?: unknown }; +} + +export class McpServer { + private readonly registry: Map; + private readonly authToken: string | undefined; + + constructor(cfg: ServerConfig) { + const bridge = new RustBridge({ + binDir: cfg.rustBinDir, + ...(cfg.timeoutMs !== undefined ? { defaultTimeoutMs: cfg.timeoutMs } : {}), + }); + this.registry = buildRegistry(bridge); + this.authToken = cfg.authToken; + } + + async loadAdapters(logger?: (msg: string) => void): Promise<{ loaded: string[]; skipped: string[] }> { + return loadAllAdapters((tool) => this.registry.set(tool.name, tool), logger); + } + + listTools(): Array<{ name: string; description: string }> { + return Array.from(this.registry.values()).map((t) => ({ + name: t.name, + description: t.description, + })); + } + + async handle(call: JsonRpcCall): Promise { + try { + this.checkAuth(call.authHeader); + const tool = lookupTool(this.registry, call.tool); + const args = this.validateArgs(tool, call.args ?? {}); + const out = await tool.handler(args); + return { ok: true, result: out }; + } catch (err) { + return { ok: false, error: toErrorPayload(err) }; + } + } + + private checkAuth(header: string | undefined): void { + if (!this.authToken) return; // auth disabled (stdio mode) + if (!header) throw new AuthError("missing auth token"); + if (!safeEqual(header, this.authToken)) throw new AuthError("invalid auth token"); + } + + private validateArgs( + tool: ToolDefinition, + raw: Record, + ): Record { + const parsed = tool.inputSchema.safeParse(raw); + if (!parsed.success) { + throw new SchemaError(parsed.error.message, { tool: tool.name }); + } + return parsed.data as Record; + } +} + +function safeEqual(a: string, b: string): boolean { + const ba = Buffer.from(a); + const bb = Buffer.from(b); + if (ba.length !== bb.length) return false; + return crypto.timingSafeEqual(ba, bb); +} + +// Exported for tests +export const __testing__ = { safeEqual, schema: z }; diff --git a/_ts_packages/packages/mcp-server/src/tool-registry.ts b/_ts_packages/packages/mcp-server/src/tool-registry.ts new file mode 100644 index 0000000..8ff06d0 --- /dev/null +++ b/_ts_packages/packages/mcp-server/src/tool-registry.ts @@ -0,0 +1,88 @@ +// Tool registry: auto-register each Rust primitive CLI as one MCP tool. +// Plus the meta-tool kei(query) that routes natural language via kei-router. + +import { z } from "zod"; +import { jsonArgsToCli, RustBridge } from "./rust-bridge.js"; +import { ToolNotFoundError } from "./errors.js"; + +export interface ToolDefinition { + name: string; + description: string; + inputSchema: z.ZodObject>; + handler: (args: Record) => Promise; +} + +// Primitive CLIs exposed 1:1 as tools. Each Rust binary accepts flags as +// --kebab-case; tool names stay snake_case for MCP convention. +export const RUST_PRIMITIVE_TOOLS: ReadonlyArray<{ binary: string; desc: string }> = [ + { binary: "kei-ledger", desc: "Append-only event ledger; sign, verify, append, list." }, + { binary: "kei-memory", desc: "Local key-value memory store with SQLite backend." }, + { binary: "kei-store", desc: "Content-addressed blob store." }, + { binary: "kei-graph-check", desc: "Validate graph invariants in a project." }, + { binary: "kei-refactor-engine", desc: "Apply structural refactors from a plan file." }, + { binary: "kei-conflict-scan", desc: "Scan a tree for merge/rebase conflict markers." }, + { binary: "kei-migrate", desc: "Run schema or directory migrations." }, + { binary: "kei-changelog", desc: "Generate changelog from commit/tag history." }, + { binary: "genesis-scan", desc: "Scan a tree for Genesis/patent-sensitive patterns." }, + { binary: "ssh-check", desc: "Validate SSH config + known_hosts consistency." }, + { binary: "firewall-diff", desc: "Diff two firewall rule dumps." }, + { binary: "tokens-sync", desc: "Sync design tokens from Figma export to code." }, + { binary: "visual-diff", desc: "Compare rendered screenshots pixel-wise." }, + { binary: "mock-render", desc: "Render HTML mock templates for preview." }, +]; + +export function buildRegistry(bridge: RustBridge): Map { + const map = new Map(); + for (const t of RUST_PRIMITIVE_TOOLS) map.set(t.binary, wrapPrimitive(bridge, t)); + map.set("kei", buildKeiMetaTool(bridge)); + return map; +} + +function wrapPrimitive( + bridge: RustBridge, + entry: { binary: string; desc: string }, +): ToolDefinition { + return { + name: entry.binary, + description: entry.desc, + inputSchema: z.object({ args: z.record(z.unknown()).optional() }), + handler: async (rawArgs) => { + const parsed = (rawArgs["args"] as Record | undefined) ?? {}; + const cli = jsonArgsToCli(parsed); + const result = await bridge.call({ binary: entry.binary, args: cli }); + if (result.exitCode !== 0) { + return `exit=${result.exitCode}\nstderr=${result.stderr}\nstdout=${result.stdout}`; + } + return result.stdout; + }, + }; +} + +function buildKeiMetaTool(bridge: RustBridge): ToolDefinition { + return { + name: "kei", + description: + "Meta-tool: routes a natural-language query to the right primitive via kei-router.", + inputSchema: z.object({ query: z.string().min(1) }), + handler: async (rawArgs) => { + const query = String(rawArgs["query"] ?? ""); + const result = await bridge.call({ + binary: "kei-router", + args: ["--query", query], + }); + if (result.exitCode !== 0) { + return `router failed exit=${result.exitCode}\nstderr=${result.stderr}`; + } + return result.stdout; + }, + }; +} + +export function lookupTool( + registry: ReadonlyMap, + name: string, +): ToolDefinition { + const t = registry.get(name); + if (!t) throw new ToolNotFoundError(name); + return t; +} diff --git a/_ts_packages/packages/mcp-server/test/errors.test.ts b/_ts_packages/packages/mcp-server/test/errors.test.ts new file mode 100644 index 0000000..680a69d --- /dev/null +++ b/_ts_packages/packages/mcp-server/test/errors.test.ts @@ -0,0 +1,54 @@ +import { describe, it, expect } from "vitest"; +import { + AuthError, + McpServerError, + RustBridgeError, + SchemaError, + ToolNotFoundError, + TimeoutError, + isMcpError, + toErrorPayload, +} from "../src/errors.js"; + +describe("errors hierarchy", () => { + it("AuthError has JSON-RPC code -32001", () => { + const e = new AuthError(); + expect(e).toBeInstanceOf(McpServerError); + expect(e.code).toBe(-32001); + expect(isMcpError(e)).toBe(true); + }); + + it("ToolNotFoundError carries the tool name in data", () => { + const e = new ToolNotFoundError("kei-foo"); + expect(e.code).toBe(-32601); + expect((e.data as { tool: string }).tool).toBe("kei-foo"); + }); + + it("RustBridgeError prefixes message", () => { + const e = new RustBridgeError("spawn failed"); + expect(e.message).toContain("rust bridge"); + }); + + it("SchemaError has JSON-RPC code -32602", () => { + const e = new SchemaError("bad input"); + expect(e.code).toBe(-32602); + }); + + it("TimeoutError records ms and tool", () => { + const e = new TimeoutError("kei-ledger", 1234); + expect(e.code).toBe(-32003); + expect((e.data as { ms: number }).ms).toBe(1234); + }); + + it("toErrorPayload handles MCP errors", () => { + const p = toErrorPayload(new AuthError("nope")); + expect(p.code).toBe(-32001); + expect(p.message).toBe("nope"); + }); + + it("toErrorPayload handles plain Errors", () => { + const p = toErrorPayload(new Error("boom")); + expect(p.code).toBe(-32000); + expect(p.message).toBe("boom"); + }); +}); diff --git a/_ts_packages/packages/mcp-server/test/kei-routing.test.ts b/_ts_packages/packages/mcp-server/test/kei-routing.test.ts new file mode 100644 index 0000000..528adcc --- /dev/null +++ b/_ts_packages/packages/mcp-server/test/kei-routing.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from "vitest"; +import { McpServer } from "../src/server.js"; + +describe("kei() meta-tool routing", () => { + it("rejects empty query via zod validation", async () => { + const srv = new McpServer({ rustBinDir: "/tmp/stub" }); + const resp = await srv.handle({ tool: "kei", args: { query: "" } }); + expect(resp.ok).toBe(false); + expect(resp.error?.code).toBe(-32602); + }); + + it("rejects missing query via zod validation", async () => { + const srv = new McpServer({ rustBinDir: "/tmp/stub" }); + const resp = await srv.handle({ tool: "kei", args: {} }); + expect(resp.ok).toBe(false); + expect(resp.error?.code).toBe(-32602); + }); + + it("accepts a non-empty query and routes via kei-router (resolves with non-zero exit)", async () => { + const srv = new McpServer({ rustBinDir: "/tmp/stub" }); + const resp = await srv.handle({ tool: "kei", args: { query: "list ledger entries" } }); + // Schema passes → meta-tool runs → router binary missing → handler formats result string. + expect(resp.ok).toBe(true); + expect(resp.result).toContain("router failed"); + }); +}); diff --git a/_ts_packages/packages/mcp-server/test/rust-bridge.test.ts b/_ts_packages/packages/mcp-server/test/rust-bridge.test.ts new file mode 100644 index 0000000..8b56d09 --- /dev/null +++ b/_ts_packages/packages/mcp-server/test/rust-bridge.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from "vitest"; +import { jsonArgsToCli, RustBridge } from "../src/rust-bridge.js"; +import { RustBridgeError } from "../src/errors.js"; + +describe("jsonArgsToCli", () => { + it("converts snake_case keys to --kebab-case flags", () => { + expect(jsonArgsToCli({ foo_bar: "value" })).toEqual(["--foo-bar", "value"]); + }); + + it("emits booleans as presence-only flags", () => { + expect(jsonArgsToCli({ verbose: true })).toEqual(["--verbose"]); + expect(jsonArgsToCli({ verbose: false })).toEqual([]); + }); + + it("skips null and undefined values", () => { + expect(jsonArgsToCli({ a: null, b: undefined, c: "x" })).toEqual(["--c", "x"]); + }); + + it("stringifies numeric values", () => { + expect(jsonArgsToCli({ count: 42 })).toEqual(["--count", "42"]); + }); +}); + +describe("RustBridge binary resolution", () => { + it("rejects illegal binary names", async () => { + const bridge = new RustBridge({ binDir: "/tmp" }); + await expect(bridge.call({ binary: "../etc/passwd", args: [] })).rejects.toBeInstanceOf( + RustBridgeError, + ); + }); + + it("accepts valid snake_case and kebab-case names (resolves with non-zero exit on ENOENT)", async () => { + const bridge = new RustBridge({ binDir: "/tmp" }); + const result = await bridge.call({ binary: "kei-ledger", args: [], timeoutMs: 500 }); + // execa is configured with reject:false → a missing binary resolves with exitCode != 0 + // (validation passed — this was the assertion under test). + expect(result.exitCode).not.toBe(0); + }); +}); diff --git a/_ts_packages/packages/mcp-server/test/server-auth.test.ts b/_ts_packages/packages/mcp-server/test/server-auth.test.ts new file mode 100644 index 0000000..d84a0c1 --- /dev/null +++ b/_ts_packages/packages/mcp-server/test/server-auth.test.ts @@ -0,0 +1,30 @@ +import { describe, it, expect } from "vitest"; +import { McpServer } from "../src/server.js"; + +describe("server auth", () => { + it("rejects calls without a token when auth is enabled", async () => { + const srv = new McpServer({ rustBinDir: "/tmp/stub", authToken: "secret" }); + const resp = await srv.handle({ tool: "kei-ledger", args: { args: {} } }); + expect(resp.ok).toBe(false); + expect(resp.error?.code).toBe(-32001); + }); + + it("rejects calls with a wrong token", async () => { + const srv = new McpServer({ rustBinDir: "/tmp/stub", authToken: "secret" }); + const resp = await srv.handle({ + tool: "kei-ledger", + args: { args: {} }, + authHeader: "wrong", + }); + expect(resp.ok).toBe(false); + expect(resp.error?.code).toBe(-32001); + }); + + it("allows calls when auth is disabled (stdio mode)", async () => { + const srv = new McpServer({ rustBinDir: "/tmp/stub" }); + const resp = await srv.handle({ tool: "does-not-exist", args: {} }); + // auth passes → fails on tool lookup instead + expect(resp.ok).toBe(false); + expect(resp.error?.code).toBe(-32601); + }); +}); diff --git a/_ts_packages/packages/mcp-server/test/server-handshake.test.ts b/_ts_packages/packages/mcp-server/test/server-handshake.test.ts new file mode 100644 index 0000000..b419f85 --- /dev/null +++ b/_ts_packages/packages/mcp-server/test/server-handshake.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from "vitest"; +import { McpServer } from "../src/server.js"; + +describe("server handshake + tool listing", () => { + it("listTools returns every primitive plus kei", () => { + const srv = new McpServer({ rustBinDir: "/tmp/stub" }); + const tools = srv.listTools(); + const names = new Set(tools.map((t) => t.name)); + expect(names.has("kei")).toBe(true); + expect(names.has("kei-ledger")).toBe(true); + expect(names.has("genesis-scan")).toBe(true); + expect(tools.length).toBeGreaterThanOrEqual(15); + }); + + it("every listed tool has a non-empty description", () => { + const srv = new McpServer({ rustBinDir: "/tmp/stub" }); + for (const t of srv.listTools()) { + expect(t.description.length).toBeGreaterThan(0); + } + }); +}); diff --git a/_ts_packages/packages/mcp-server/test/tool-registry.test.ts b/_ts_packages/packages/mcp-server/test/tool-registry.test.ts new file mode 100644 index 0000000..db234e3 --- /dev/null +++ b/_ts_packages/packages/mcp-server/test/tool-registry.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from "vitest"; +import { buildRegistry, lookupTool, RUST_PRIMITIVE_TOOLS } from "../src/tool-registry.js"; +import { RustBridge } from "../src/rust-bridge.js"; +import { ToolNotFoundError } from "../src/errors.js"; + +describe("tool registry", () => { + const bridge = new RustBridge({ binDir: "/tmp/stub" }); + const registry = buildRegistry(bridge); + + it("registers one tool per Rust primitive", () => { + for (const t of RUST_PRIMITIVE_TOOLS) { + expect(registry.has(t.binary)).toBe(true); + } + }); + + it("registers the kei meta-tool", () => { + const t = lookupTool(registry, "kei"); + expect(t.name).toBe("kei"); + expect(t.description).toContain("Meta-tool"); + }); + + it("lookupTool throws ToolNotFoundError for unknown names", () => { + expect(() => lookupTool(registry, "nonexistent-tool")).toThrow(ToolNotFoundError); + }); + + it("tool description is non-empty for each primitive", () => { + for (const t of RUST_PRIMITIVE_TOOLS) { + expect(t.desc.length).toBeGreaterThan(10); + } + }); +}); diff --git a/_ts_packages/packages/mcp-server/tsconfig.json b/_ts_packages/packages/mcp-server/tsconfig.json new file mode 100644 index 0000000..2c00eac --- /dev/null +++ b/_ts_packages/packages/mcp-server/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "test/**/*"] +} diff --git a/_ts_packages/packages/mcp-server/vitest.config.ts b/_ts_packages/packages/mcp-server/vitest.config.ts new file mode 100644 index 0000000..d9ebefd --- /dev/null +++ b/_ts_packages/packages/mcp-server/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + environment: "node", + }, +}); diff --git a/_ts_packages/packages/recall-adapter/package.json b/_ts_packages/packages/recall-adapter/package.json new file mode 100644 index 0000000..568de61 --- /dev/null +++ b/_ts_packages/packages/recall-adapter/package.json @@ -0,0 +1,32 @@ +{ + "name": "@keisei/recall-adapter", + "version": "0.14.0", + "description": "Recall.ai adapter (Zoom meeting capture) for the KeiSei MCP server", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -b", + "test": "vitest run" + }, + "dependencies": { + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/_ts_packages/packages/recall-adapter/src/client.ts b/_ts_packages/packages/recall-adapter/src/client.ts new file mode 100644 index 0000000..f56409b --- /dev/null +++ b/_ts_packages/packages/recall-adapter/src/client.ts @@ -0,0 +1,63 @@ +// Minimal client for the Recall.ai v1 REST API. +// Docs: https://docs.recall.ai/reference (verified 2026-04). + +export type FetchFn = typeof fetch; + +export interface RecallClientConfig { + apiKey: string; + baseUrl?: string; + fetchImpl?: FetchFn; +} + +const DEFAULT_BASE_URL = "https://api.recall.ai/api/v1"; + +export class RecallClient { + private readonly apiKey: string; + private readonly baseUrl: string; + private readonly fetchImpl: FetchFn; + + constructor(cfg: RecallClientConfig) { + if (!cfg.apiKey) throw new Error("RECALL_API_KEY is required"); + this.apiKey = cfg.apiKey; + this.baseUrl = cfg.baseUrl ?? DEFAULT_BASE_URL; + this.fetchImpl = cfg.fetchImpl ?? fetch; + } + + async listBots(): Promise { + return this.request("GET", "/bot/"); + } + + async getBot(botId: string): Promise { + return this.request("GET", `/bot/${encodeURIComponent(botId)}/`); + } + + async joinMeeting(meetingUrl: string, botName = "KeiSei"): Promise { + return this.request("POST", "/bot/", { meeting_url: meetingUrl, bot_name: botName }); + } + + async leaveMeeting(botId: string): Promise { + return this.request("POST", `/bot/${encodeURIComponent(botId)}/leave_call/`); + } + + async getTranscript(botId: string): Promise { + return this.request("GET", `/bot/${encodeURIComponent(botId)}/transcript/`); + } + + private async request(method: string, path: string, body?: unknown): Promise { + const headers: Record = { + Authorization: `Token ${this.apiKey}`, + Accept: "application/json", + }; + const init: RequestInit = { method, headers }; + if (body !== undefined) { + headers["Content-Type"] = "application/json"; + init.body = JSON.stringify(body); + } + const res = await this.fetchImpl(`${this.baseUrl}${path}`, init); + if (!res.ok) { + const text = await res.text(); + throw new Error(`recall ${method} ${path} -> ${res.status}: ${text}`); + } + return res.json(); + } +} diff --git a/_ts_packages/packages/recall-adapter/src/index.ts b/_ts_packages/packages/recall-adapter/src/index.ts new file mode 100644 index 0000000..9a190e1 --- /dev/null +++ b/_ts_packages/packages/recall-adapter/src/index.ts @@ -0,0 +1,19 @@ +import { RecallClient } from "./client.js"; +import { buildRecallTools, type RecallTool } from "./tools.js"; + +export { RecallClient } from "./client.js"; +export { buildRecallTools } from "./tools.js"; +export type { RecallTool } from "./tools.js"; + +type Registrar = (tool: RecallTool) => void; + +export function registerAdapter(register: Registrar): void { + const apiKey = process.env["RECALL_API_KEY"]; + if (!apiKey) { + throw new Error( + "RECALL_API_KEY env var is missing; set it in ~/.claude/secrets/.env (RULE 0.8).", + ); + } + const client = new RecallClient({ apiKey }); + for (const tool of buildRecallTools(client)) register(tool); +} diff --git a/_ts_packages/packages/recall-adapter/src/tools.ts b/_ts_packages/packages/recall-adapter/src/tools.ts new file mode 100644 index 0000000..8917157 --- /dev/null +++ b/_ts_packages/packages/recall-adapter/src/tools.ts @@ -0,0 +1,69 @@ +// Recall.ai tool definitions. Each tool wraps one client method and returns +// JSON-stringified output for the MCP transport. + +import { z } from "zod"; +import { RecallClient } from "./client.js"; + +export interface RecallTool { + name: string; + description: string; + inputSchema: z.ZodObject>; + handler: (args: Record) => Promise; +} + +const BotIdArgs = z.object({ bot_id: z.string().min(1) }); +const JoinArgs = z.object({ + meeting_url: z.string().url(), + bot_name: z.string().optional(), +}); + +export function buildRecallTools(client: RecallClient): RecallTool[] { + return [ + { + name: "zoom_status", + description: "Status of a deployed Recall.ai bot (bot_id required).", + inputSchema: BotIdArgs, + handler: async (raw) => { + const args = BotIdArgs.parse(raw); + return pretty(await client.getBot(args.bot_id)); + }, + }, + { + name: "zoom_bots", + description: "List all Recall.ai bots for this account.", + inputSchema: z.object({}), + handler: async () => pretty(await client.listBots()), + }, + { + name: "zoom_join", + description: "Deploy a Recall.ai bot to a meeting URL.", + inputSchema: JoinArgs, + handler: async (raw) => { + const args = JoinArgs.parse(raw); + return pretty(await client.joinMeeting(args.meeting_url, args.bot_name)); + }, + }, + { + name: "zoom_leave", + description: "Recall an active bot from a meeting.", + inputSchema: BotIdArgs, + handler: async (raw) => { + const args = BotIdArgs.parse(raw); + return pretty(await client.leaveMeeting(args.bot_id)); + }, + }, + { + name: "zoom_chat", + description: "Fetch transcript for a bot's meeting.", + inputSchema: BotIdArgs, + handler: async (raw) => { + const args = BotIdArgs.parse(raw); + return pretty(await client.getTranscript(args.bot_id)); + }, + }, + ]; +} + +function pretty(x: unknown): string { + return JSON.stringify(x, null, 2); +} diff --git a/_ts_packages/packages/recall-adapter/test/client.test.ts b/_ts_packages/packages/recall-adapter/test/client.test.ts new file mode 100644 index 0000000..718aee8 --- /dev/null +++ b/_ts_packages/packages/recall-adapter/test/client.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect, vi } from "vitest"; +import { RecallClient } from "../src/client.js"; + +function makeFetchMock(payload: unknown, ok = true, status = 200): typeof fetch { + return vi.fn(async () => { + return { + ok, + status, + async text() { return JSON.stringify(payload); }, + async json() { return payload; }, + } as unknown as Response; + }) as unknown as typeof fetch; +} + +describe("RecallClient", () => { + it("rejects empty API key", () => { + expect(() => new RecallClient({ apiKey: "" })).toThrow(/RECALL_API_KEY/); + }); + + it("listBots calls GET /bot/", async () => { + const fetchImpl = makeFetchMock([{ id: "b1" }]); + const c = new RecallClient({ apiKey: "k", fetchImpl }); + const out = (await c.listBots()) as Array<{ id: string }>; + expect(out[0]?.id).toBe("b1"); + expect(fetchImpl).toHaveBeenCalledOnce(); + }); + + it("joinMeeting POSTs meeting_url", async () => { + const fetchImpl = makeFetchMock({ id: "new" }); + const c = new RecallClient({ apiKey: "k", fetchImpl }); + const out = await c.joinMeeting("https://zoom.us/j/123"); + expect((out as { id: string }).id).toBe("new"); + }); + + it("propagates non-2xx responses as Error", async () => { + const fetchImpl = makeFetchMock({ detail: "forbidden" }, false, 403); + const c = new RecallClient({ apiKey: "k", fetchImpl }); + await expect(c.getBot("b1")).rejects.toThrow(/403/); + }); +}); diff --git a/_ts_packages/packages/recall-adapter/test/tools.test.ts b/_ts_packages/packages/recall-adapter/test/tools.test.ts new file mode 100644 index 0000000..942b153 --- /dev/null +++ b/_ts_packages/packages/recall-adapter/test/tools.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect, vi } from "vitest"; +import { RecallClient } from "../src/client.js"; +import { buildRecallTools } from "../src/tools.js"; + +function makeFetchMock(payload: unknown): typeof fetch { + return vi.fn(async () => ({ + ok: true, + status: 200, + async text() { return JSON.stringify(payload); }, + async json() { return payload; }, + } as unknown as Response)) as unknown as typeof fetch; +} + +describe("recall tool surface", () => { + it("exposes 5 tools", () => { + const c = new RecallClient({ apiKey: "k", fetchImpl: makeFetchMock({}) }); + const tools = buildRecallTools(c); + expect(tools.map((t) => t.name)).toEqual([ + "zoom_status", + "zoom_bots", + "zoom_join", + "zoom_leave", + "zoom_chat", + ]); + }); + + it("zoom_join validates meeting_url as URL", async () => { + const c = new RecallClient({ apiKey: "k", fetchImpl: makeFetchMock({}) }); + const tool = buildRecallTools(c).find((t) => t.name === "zoom_join"); + await expect(tool!.handler({ meeting_url: "not a url" })).rejects.toBeTruthy(); + }); + + it("zoom_status returns JSON string", async () => { + const c = new RecallClient({ apiKey: "k", fetchImpl: makeFetchMock({ id: "x" }) }); + const tool = buildRecallTools(c).find((t) => t.name === "zoom_status"); + const out = await tool!.handler({ bot_id: "x" }); + expect(out).toContain('"id": "x"'); + }); + + it("zoom_bots hits list endpoint", async () => { + const fetchImpl = makeFetchMock([{ id: "a" }]); + const c = new RecallClient({ apiKey: "k", fetchImpl }); + const tool = buildRecallTools(c).find((t) => t.name === "zoom_bots"); + const out = await tool!.handler({}); + expect(out).toContain("a"); + }); +}); diff --git a/_ts_packages/packages/recall-adapter/tsconfig.json b/_ts_packages/packages/recall-adapter/tsconfig.json new file mode 100644 index 0000000..2c00eac --- /dev/null +++ b/_ts_packages/packages/recall-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "test/**/*"] +} diff --git a/_ts_packages/packages/recall-adapter/vitest.config.ts b/_ts_packages/packages/recall-adapter/vitest.config.ts new file mode 100644 index 0000000..d9ebefd --- /dev/null +++ b/_ts_packages/packages/recall-adapter/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + environment: "node", + }, +}); diff --git a/_ts_packages/packages/telegram-adapter/package.json b/_ts_packages/packages/telegram-adapter/package.json new file mode 100644 index 0000000..1856eb7 --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/package.json @@ -0,0 +1,33 @@ +{ + "name": "@keisei/telegram-adapter", + "version": "0.14.0", + "description": "Telegram Bot API adapter for the KeiSei MCP server", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -b", + "test": "vitest run" + }, + "dependencies": { + "grammy": "^1.28.0", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/_ts_packages/packages/telegram-adapter/src/client.ts b/_ts_packages/packages/telegram-adapter/src/client.ts new file mode 100644 index 0000000..c9736fd --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/src/client.ts @@ -0,0 +1,74 @@ +// Thin wrapper over grammy's Bot class. One class = one responsibility: +// own the Bot instance, expose a narrow surface used by tool handlers. + +import { Bot, InputFile } from "grammy"; +import type { ContactRecord, GroupRecord } from "./types.js"; + +export interface TelegramClientConfig { + token: string; +} + +export class TelegramClient { + private readonly bot: Bot; + private readonly contactsCache: Map = new Map(); + private readonly groupsCache: Map = new Map(); + + constructor(cfg: TelegramClientConfig) { + if (!cfg.token) throw new Error("TELEGRAM_BOT_TOKEN is required"); + this.bot = new Bot(cfg.token); + } + + async status(): Promise<{ username: string; id: number }> { + const me = await this.bot.api.getMe(); + return { username: me.username, id: me.id }; + } + + async chatInfo(chat: string | number): Promise<{ id: number; title: string; type: string }> { + const info = await this.bot.api.getChat(chat); + const title = "title" in info && info.title ? info.title : + "first_name" in info && info.first_name ? info.first_name : String(info.id); + return { id: info.id, title, type: info.type }; + } + + async sendText(chat: string | number, text: string): Promise { + const msg = await this.bot.api.sendMessage(chat, text); + return msg.message_id; + } + + async sendDocument(chat: string | number, filePath: string, caption?: string): Promise { + const msg = await this.bot.api.sendDocument(chat, new InputFile(filePath), caption !== undefined ? { caption } : {}); + return msg.message_id; + } + + async sendPhoto(chat: string | number, filePath: string, caption?: string): Promise { + const msg = await this.bot.api.sendPhoto(chat, new InputFile(filePath), caption !== undefined ? { caption } : {}); + return msg.message_id; + } + + async sendVideo(chat: string | number, filePath: string, caption?: string): Promise { + const msg = await this.bot.api.sendVideo(chat, new InputFile(filePath), caption !== undefined ? { caption } : {}); + return msg.message_id; + } + + async sendVoice(chat: string | number, filePath: string, caption?: string): Promise { + const msg = await this.bot.api.sendVoice(chat, new InputFile(filePath), caption !== undefined ? { caption } : {}); + return msg.message_id; + } + + listGroups(): GroupRecord[] { + return Array.from(this.groupsCache.values()); + } + + listContacts(): ContactRecord[] { + return Array.from(this.contactsCache.values()); + } + + // Test helpers to seed cache; kept internal via underscore prefix. + _seedContact(c: ContactRecord): void { + this.contactsCache.set(c.userId, c); + } + + _seedGroup(g: GroupRecord): void { + this.groupsCache.set(g.chatId, g); + } +} diff --git a/_ts_packages/packages/telegram-adapter/src/index.ts b/_ts_packages/packages/telegram-adapter/src/index.ts new file mode 100644 index 0000000..8307940 --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/src/index.ts @@ -0,0 +1,23 @@ +// Public entry: exports registerAdapter() for the MCP server loader, +// plus the class + tool builder for programmatic use. + +import { TelegramClient } from "./client.js"; +import { buildTelegramTools, type TelegramTool } from "./tools.js"; + +export { TelegramClient } from "./client.js"; +export { buildTelegramTools } from "./tools.js"; +export type { TelegramTool } from "./tools.js"; +export * from "./types.js"; + +type Registrar = (tool: TelegramTool) => void; + +export function registerAdapter(register: Registrar): void { + const token = process.env["TELEGRAM_BOT_TOKEN"]; + if (!token) { + throw new Error( + "TELEGRAM_BOT_TOKEN env var is missing; set it in ~/.claude/secrets/.env (RULE 0.8).", + ); + } + const client = new TelegramClient({ token }); + for (const tool of buildTelegramTools(client)) register(tool); +} diff --git a/_ts_packages/packages/telegram-adapter/src/tools.ts b/_ts_packages/packages/telegram-adapter/src/tools.ts new file mode 100644 index 0000000..52d345e --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/src/tools.ts @@ -0,0 +1,112 @@ +// Tool definitions for the Telegram adapter. Each tool is a small wrapper +// around TelegramClient + a zod schema, returning a string to MCP. + +import { z } from "zod"; +import { TelegramClient } from "./client.js"; +import { + ChatInfoArgs, + SendFileArgs, + SendTextArgs, + SendVoiceArgs, +} from "./types.js"; + +export interface TelegramTool { + name: string; + description: string; + inputSchema: z.ZodObject>; + handler: (args: Record) => Promise; +} + +export function buildTelegramTools(client: TelegramClient): TelegramTool[] { + return [ + { + name: "telegram_status", + description: "Telegram bot identity and connectivity.", + inputSchema: z.object({}), + handler: async () => { + const s = await client.status(); + return `bot=@${s.username} id=${s.id}`; + }, + }, + { + name: "telegram_groups", + description: "List groups the bot has observed.", + inputSchema: z.object({}), + handler: async () => { + const gs = client.listGroups(); + if (gs.length === 0) return "No groups tracked yet."; + return gs.map((g) => `${g.chatId} | ${g.title} [${g.type}]`).join("\n"); + }, + }, + { + name: "telegram_contacts", + description: "List known Telegram contacts.", + inputSchema: z.object({}), + handler: async () => { + const cs = client.listContacts(); + if (cs.length === 0) return "No contacts yet."; + return cs.map(formatContact).join("\n"); + }, + }, + { + name: "telegram_chat_info", + description: "Chat metadata for a given chat ID or @username.", + inputSchema: ChatInfoArgs, + handler: async (raw) => { + const args = ChatInfoArgs.parse(raw); + const info = await client.chatInfo(args.chat); + return `id=${info.id}\ntitle=${info.title}\ntype=${info.type}`; + }, + }, + { + name: "telegram_send", + description: "Send a text message.", + inputSchema: SendTextArgs, + handler: async (raw) => { + const args = SendTextArgs.parse(raw); + const id = await client.sendText(args.chat, args.text); + return `sent message_id=${id}`; + }, + }, + { + name: "telegram_send_file", + description: "Send a document, photo, or video file.", + inputSchema: SendFileArgs, + handler: async (raw) => { + const args = SendFileArgs.parse(raw); + const id = await dispatchFile(client, args); + return `sent ${args.kind} message_id=${id}`; + }, + }, + { + name: "telegram_send_voice", + description: "Send a pre-recorded voice note file.", + inputSchema: SendVoiceArgs, + handler: async (raw) => { + const args = SendVoiceArgs.parse(raw); + const id = await client.sendVoice(args.chat, args.file, args.caption); + return `sent voice message_id=${id}`; + }, + }, + ]; +} + +function formatContact(c: { + userId: number; + firstName: string; + lastName?: string | undefined; + username?: string | undefined; +}): string { + const name = c.lastName ? `${c.firstName} ${c.lastName}` : c.firstName; + const handle = c.username ? ` @${c.username}` : ""; + return `${c.userId} | ${name}${handle}`; +} + +async function dispatchFile( + client: TelegramClient, + args: { chat: string | number; file: string; kind: "document" | "photo" | "video"; caption?: string | undefined }, +): Promise { + if (args.kind === "photo") return client.sendPhoto(args.chat, args.file, args.caption); + if (args.kind === "video") return client.sendVideo(args.chat, args.file, args.caption); + return client.sendDocument(args.chat, args.file, args.caption); +} diff --git a/_ts_packages/packages/telegram-adapter/src/types.ts b/_ts_packages/packages/telegram-adapter/src/types.ts new file mode 100644 index 0000000..ab77be4 --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/src/types.ts @@ -0,0 +1,51 @@ +// Tool I/O types for the Telegram adapter. Kept separate so both the +// adapter and consumers can import schemas without pulling grammy. + +import { z } from "zod"; + +export const TelegramChatRef = z.union([ + z.number().int(), + z.string().min(1), +]); +export type TelegramChatRef = z.infer; + +export const SendTextArgs = z.object({ + chat: TelegramChatRef, + text: z.string().min(1), +}); +export type SendTextArgs = z.infer; + +export const SendFileArgs = z.object({ + chat: TelegramChatRef, + file: z.string().min(1), + kind: z.enum(["document", "photo", "video"]).default("document"), + caption: z.string().optional(), +}); +export type SendFileArgs = z.infer; + +export const SendVoiceArgs = z.object({ + chat: TelegramChatRef, + file: z.string().min(1), + caption: z.string().optional(), +}); +export type SendVoiceArgs = z.infer; + +export const ChatInfoArgs = z.object({ + chat: TelegramChatRef, +}); +export type ChatInfoArgs = z.infer; + +export interface ContactRecord { + userId: number; + firstName: string; + lastName?: string | undefined; + username?: string | undefined; + lastSeen?: number | undefined; +} + +export interface GroupRecord { + chatId: number; + title: string; + type: string; + lastMsg?: number | undefined; +} diff --git a/_ts_packages/packages/telegram-adapter/test/client.test.ts b/_ts_packages/packages/telegram-adapter/test/client.test.ts new file mode 100644 index 0000000..ff8dcd0 --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/test/client.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from "vitest"; +import { TelegramClient } from "../src/client.js"; + +describe("TelegramClient cache", () => { + it("rejects empty token at construction", () => { + expect(() => new TelegramClient({ token: "" })).toThrow(/TELEGRAM_BOT_TOKEN/); + }); + + it("listGroups is empty by default", () => { + const c = new TelegramClient({ token: "123:ABC" }); + expect(c.listGroups()).toEqual([]); + }); + + it("listContacts is empty by default", () => { + const c = new TelegramClient({ token: "123:ABC" }); + expect(c.listContacts()).toEqual([]); + }); + + it("_seedContact and _seedGroup populate caches", () => { + const c = new TelegramClient({ token: "123:ABC" }); + c._seedContact({ userId: 99, firstName: "Alice" }); + c._seedGroup({ chatId: -100, title: "Test", type: "supergroup" }); + expect(c.listContacts()).toHaveLength(1); + expect(c.listGroups()).toHaveLength(1); + }); +}); diff --git a/_ts_packages/packages/telegram-adapter/test/tools.test.ts b/_ts_packages/packages/telegram-adapter/test/tools.test.ts new file mode 100644 index 0000000..04ce562 --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/test/tools.test.ts @@ -0,0 +1,44 @@ +import { describe, it, expect } from "vitest"; +import { TelegramClient } from "../src/client.js"; +import { buildTelegramTools } from "../src/tools.js"; + +describe("telegram tool surface", () => { + const c = new TelegramClient({ token: "123:ABC" }); + const tools = buildTelegramTools(c); + + it("exposes 7 tools total", () => { + expect(tools.map((t) => t.name)).toEqual([ + "telegram_status", + "telegram_groups", + "telegram_contacts", + "telegram_chat_info", + "telegram_send", + "telegram_send_file", + "telegram_send_voice", + ]); + }); + + it("telegram_groups returns placeholder text when empty", async () => { + const tool = tools.find((t) => t.name === "telegram_groups"); + const out = await tool!.handler({}); + expect(out).toContain("No groups"); + }); + + it("telegram_contacts formats seeded contact", async () => { + c._seedContact({ userId: 42, firstName: "Bob", username: "bobby" }); + const tool = tools.find((t) => t.name === "telegram_contacts"); + const out = await tool!.handler({}); + expect(out).toContain("42"); + expect(out).toContain("Bob"); + expect(out).toContain("@bobby"); + }); + + it("telegram_send rejects missing args via schema", async () => { + const tool = tools.find((t) => t.name === "telegram_send"); + await expect(tool!.handler({})).rejects.toBeTruthy(); + }); + + it("tool descriptions are non-empty", () => { + for (const t of tools) expect(t.description.length).toBeGreaterThan(0); + }); +}); diff --git a/_ts_packages/packages/telegram-adapter/test/types.test.ts b/_ts_packages/packages/telegram-adapter/test/types.test.ts new file mode 100644 index 0000000..b2e6cab --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/test/types.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from "vitest"; +import { SendTextArgs, SendFileArgs, SendVoiceArgs, ChatInfoArgs } from "../src/types.js"; + +describe("zod schemas", () => { + it("SendTextArgs accepts numeric chat id", () => { + const r = SendTextArgs.safeParse({ chat: 12345, text: "hi" }); + expect(r.success).toBe(true); + }); + + it("SendTextArgs accepts string chat handle", () => { + const r = SendTextArgs.safeParse({ chat: "@username", text: "hi" }); + expect(r.success).toBe(true); + }); + + it("SendTextArgs rejects empty text", () => { + const r = SendTextArgs.safeParse({ chat: 1, text: "" }); + expect(r.success).toBe(false); + }); + + it("SendFileArgs defaults kind to document", () => { + const r = SendFileArgs.safeParse({ chat: 1, file: "/x" }); + expect(r.success).toBe(true); + if (r.success) expect(r.data.kind).toBe("document"); + }); + + it("SendFileArgs rejects unknown kind", () => { + const r = SendFileArgs.safeParse({ chat: 1, file: "/x", kind: "sticker" }); + expect(r.success).toBe(false); + }); + + it("SendVoiceArgs requires file path", () => { + const r = SendVoiceArgs.safeParse({ chat: 1 }); + expect(r.success).toBe(false); + }); + + it("ChatInfoArgs requires chat", () => { + const r = ChatInfoArgs.safeParse({}); + expect(r.success).toBe(false); + }); +}); diff --git a/_ts_packages/packages/telegram-adapter/tsconfig.json b/_ts_packages/packages/telegram-adapter/tsconfig.json new file mode 100644 index 0000000..2c00eac --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "test/**/*"] +} diff --git a/_ts_packages/packages/telegram-adapter/vitest.config.ts b/_ts_packages/packages/telegram-adapter/vitest.config.ts new file mode 100644 index 0000000..d9ebefd --- /dev/null +++ b/_ts_packages/packages/telegram-adapter/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + environment: "node", + }, +}); diff --git a/_ts_packages/packages/youtube-adapter/package.json b/_ts_packages/packages/youtube-adapter/package.json new file mode 100644 index 0000000..e1363b2 --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/package.json @@ -0,0 +1,34 @@ +{ + "name": "@keisei/youtube-adapter", + "version": "0.14.0", + "description": "YouTube Data API v3 adapter for the KeiSei MCP server", + "type": "module", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc -b", + "test": "vitest run" + }, + "dependencies": { + "googleapis": "^144.0.0", + "youtube-transcript": "^1.2.1", + "zod": "^3.23.0" + }, + "devDependencies": { + "@types/node": "^20.0.0", + "typescript": "^5.5.0", + "vitest": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/_ts_packages/packages/youtube-adapter/src/client.ts b/_ts_packages/packages/youtube-adapter/src/client.ts new file mode 100644 index 0000000..e74796e --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/src/client.ts @@ -0,0 +1,112 @@ +// YouTube Data API v3 client wrapper. The surface is intentionally narrow — +// subscriptions list, search, videos.list(statistics), plus a transcript +// helper using the `youtube-transcript` package. + +import { google } from "googleapis"; +import type { TranscriptLine, VideoStats, VideoSummary } from "./types.js"; + +export interface YouTubeClientConfig { + apiKey: string; + surface?: YouTubeSurface; + transcriptFn?: TranscriptFn; +} + +export type TranscriptFn = (videoId: string) => Promise; + +export interface YouTubeSurface { + subscriptions: (max: number) => Promise; + channelVideos: (channelId: string, since: string | undefined, max: number) => Promise; + search: (query: string, max: number) => Promise; + stats: (videoId: string) => Promise; +} + +export class YouTubeClient { + private readonly surface: YouTubeSurface; + private readonly transcriptFn: TranscriptFn; + + constructor(cfg: YouTubeClientConfig) { + this.surface = cfg.surface ?? buildDefaultSurface(cfg.apiKey); + this.transcriptFn = cfg.transcriptFn ?? defaultTranscriptFn; + } + + subscriptions(max: number): Promise { + return this.surface.subscriptions(max); + } + + newVideos(channelId: string, since: string | undefined, max: number): Promise { + return this.surface.channelVideos(channelId, since, max); + } + + search(query: string, max: number): Promise { + return this.surface.search(query, max); + } + + stats(videoId: string): Promise { + return this.surface.stats(videoId); + } + + transcript(videoId: string): Promise { + return this.transcriptFn(videoId); + } +} + +interface TranscriptModule { + YoutubeTranscript: { + fetchTranscript: (videoId: string) => Promise>; + }; +} + +async function defaultTranscriptFn(videoId: string): Promise { + // Deferred import: the upstream package ships dual-module with a broken + // CJS entry, so eager `import` at top-level fails under ESM + vitest. + const mod = (await import("youtube-transcript")) as unknown as TranscriptModule; + const rows = await mod.YoutubeTranscript.fetchTranscript(videoId); + return rows.map((r) => ({ text: r.text, offset: r.offset, duration: r.duration })); +} + +function buildDefaultSurface(apiKey: string): YouTubeSurface { + if (!apiKey) throw new Error("YOUTUBE_API_KEY is required"); + const yt = google.youtube({ version: "v3", auth: apiKey }); + return { + subscriptions: async (max) => { + const res = await yt.subscriptions.list({ part: ["snippet"], mine: true, maxResults: max }); + return (res.data.items ?? []).map(itemToSummary); + }, + channelVideos: async (channelId, since, max) => { + const res = await yt.search.list({ + part: ["snippet"], + channelId, + order: "date", + maxResults: max, + ...(since !== undefined ? { publishedAfter: since } : {}), + }); + return (res.data.items ?? []).map(itemToSummary); + }, + search: async (query, max) => { + const res = await yt.search.list({ part: ["snippet"], q: query, maxResults: max }); + return (res.data.items ?? []).map(itemToSummary); + }, + stats: async (videoId) => { + const res = await yt.videos.list({ part: ["statistics"], id: [videoId] }); + const s = res.data.items?.[0]?.statistics ?? {}; + return { + videoId, + viewCount: s.viewCount ?? undefined, + likeCount: s.likeCount ?? undefined, + commentCount: s.commentCount ?? undefined, + }; + }, + }; +} + +function itemToSummary(item: { id?: { videoId?: string | null } | string | null; snippet?: { title?: string | null; channelTitle?: string | null; resourceId?: { videoId?: string | null } | null; publishedAt?: string | null } | null }): VideoSummary { + const vid = typeof item.id === "object" && item.id !== null + ? (item.id.videoId ?? "") + : (item.snippet?.resourceId?.videoId ?? ""); + return { + videoId: vid, + title: item.snippet?.title ?? undefined, + channel: item.snippet?.channelTitle ?? undefined, + publishedAt: item.snippet?.publishedAt ?? undefined, + }; +} diff --git a/_ts_packages/packages/youtube-adapter/src/index.ts b/_ts_packages/packages/youtube-adapter/src/index.ts new file mode 100644 index 0000000..3e76dfb --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/src/index.ts @@ -0,0 +1,20 @@ +import { YouTubeClient } from "./client.js"; +import { buildYouTubeTools, type YouTubeTool } from "./tools.js"; + +export { YouTubeClient } from "./client.js"; +export { buildYouTubeTools } from "./tools.js"; +export type { YouTubeTool } from "./tools.js"; +export * from "./types.js"; + +type Registrar = (tool: YouTubeTool) => void; + +export function registerAdapter(register: Registrar): void { + const apiKey = process.env["YOUTUBE_API_KEY"]; + if (!apiKey) { + throw new Error( + "YOUTUBE_API_KEY env var is missing; set it in ~/.claude/secrets/.env (RULE 0.8).", + ); + } + const client = new YouTubeClient({ apiKey }); + for (const tool of buildYouTubeTools(client)) register(tool); +} diff --git a/_ts_packages/packages/youtube-adapter/src/tools.ts b/_ts_packages/packages/youtube-adapter/src/tools.ts new file mode 100644 index 0000000..d2805c8 --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/src/tools.ts @@ -0,0 +1,87 @@ +import { z } from "zod"; +import { YouTubeClient } from "./client.js"; +import { + NewVideosArgs, + SearchArgs, + SubscriptionsArgs, + VideoIdArgs, + type TranscriptLine, + type VideoStats, + type VideoSummary, +} from "./types.js"; + +export interface YouTubeTool { + name: string; + description: string; + inputSchema: z.ZodObject>; + handler: (args: Record) => Promise; +} + +export function buildYouTubeTools(client: YouTubeClient): YouTubeTool[] { + return [ + { + name: "youtube_subscriptions", + description: "List the authenticated user's channel subscriptions.", + inputSchema: SubscriptionsArgs, + handler: async (raw) => { + const args = SubscriptionsArgs.parse(raw); + return formatList(await client.subscriptions(args.max)); + }, + }, + { + name: "youtube_new_videos", + description: "Latest videos from a given channel (optional --since ISO8601).", + inputSchema: NewVideosArgs, + handler: async (raw) => { + const args = NewVideosArgs.parse(raw); + return formatList(await client.newVideos(args.channel_id, args.since, args.max)); + }, + }, + { + name: "youtube_search", + description: "Search YouTube for a query string.", + inputSchema: SearchArgs, + handler: async (raw) => { + const args = SearchArgs.parse(raw); + return formatList(await client.search(args.query, args.max)); + }, + }, + { + name: "youtube_transcript", + description: "Fetch the transcript (captions) of a video as plain text.", + inputSchema: VideoIdArgs, + handler: async (raw) => { + const args = VideoIdArgs.parse(raw); + return formatTranscript(await client.transcript(args.video_id)); + }, + }, + { + name: "youtube_video_stats", + description: "View/like/comment counts for a given video.", + inputSchema: VideoIdArgs, + handler: async (raw) => { + const args = VideoIdArgs.parse(raw); + return formatStats(await client.stats(args.video_id)); + }, + }, + ]; +} + +function formatList(items: VideoSummary[]): string { + if (items.length === 0) return "No results."; + return items.map((v) => `${v.videoId} | ${v.channel ?? "?"} | ${v.title ?? "?"}`).join("\n"); +} + +function formatTranscript(lines: TranscriptLine[]): string { + if (lines.length === 0) return "No transcript available."; + return lines.map((l) => l.text).join(" "); +} + +function formatStats(s: VideoStats): string { + return [ + `video: ${s.videoId}`, + `views: ${s.viewCount ?? "?"}`, + `likes: ${s.likeCount ?? "?"}`, + `comments: ${s.commentCount ?? "?"}`, + ].join("\n"); +} diff --git a/_ts_packages/packages/youtube-adapter/src/types.ts b/_ts_packages/packages/youtube-adapter/src/types.ts new file mode 100644 index 0000000..041cd37 --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/src/types.ts @@ -0,0 +1,46 @@ +// YouTube Data API v3 tool I/O types. + +import { z } from "zod"; + +export const SubscriptionsArgs = z.object({ + max: z.number().int().positive().max(50).default(25), +}); +export type SubscriptionsArgs = z.infer; + +export const NewVideosArgs = z.object({ + channel_id: z.string().min(1), + since: z.string().optional(), + max: z.number().int().positive().max(50).default(10), +}); +export type NewVideosArgs = z.infer; + +export const SearchArgs = z.object({ + query: z.string().min(1), + max: z.number().int().positive().max(50).default(10), +}); +export type SearchArgs = z.infer; + +export const VideoIdArgs = z.object({ + video_id: z.string().min(1), +}); +export type VideoIdArgs = z.infer; + +export interface VideoSummary { + videoId: string; + title?: string | undefined; + channel?: string | undefined; + publishedAt?: string | undefined; +} + +export interface VideoStats { + videoId: string; + viewCount?: string | undefined; + likeCount?: string | undefined; + commentCount?: string | undefined; +} + +export interface TranscriptLine { + text: string; + offset: number; + duration: number; +} diff --git a/_ts_packages/packages/youtube-adapter/test/client.test.ts b/_ts_packages/packages/youtube-adapter/test/client.test.ts new file mode 100644 index 0000000..5199427 --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/test/client.test.ts @@ -0,0 +1,37 @@ +import { describe, it, expect, vi } from "vitest"; +import { YouTubeClient, type YouTubeSurface } from "../src/client.js"; + +function makeSurface(): YouTubeSurface { + return { + subscriptions: vi.fn(async () => [{ videoId: "v1", title: "Sub channel", channel: "c1" }]), + channelVideos: vi.fn(async () => [{ videoId: "v2", title: "Latest" }]), + search: vi.fn(async () => [{ videoId: "v3", title: "Result" }]), + stats: vi.fn(async () => ({ videoId: "v1", viewCount: "100", likeCount: "5", commentCount: "1" })), + }; +} + +describe("YouTubeClient", () => { + it("subscriptions delegates to surface", async () => { + const s = makeSurface(); + const c = new YouTubeClient({ apiKey: "k", surface: s }); + const out = await c.subscriptions(10); + expect(out[0]?.videoId).toBe("v1"); + expect(s.subscriptions).toHaveBeenCalledWith(10); + }); + + it("transcript uses injected fn", async () => { + const c = new YouTubeClient({ + apiKey: "k", + surface: makeSurface(), + transcriptFn: async () => [{ text: "hi", offset: 0, duration: 1 }], + }); + const out = await c.transcript("vid"); + expect(out).toHaveLength(1); + }); + + it("stats returns video statistics", async () => { + const c = new YouTubeClient({ apiKey: "k", surface: makeSurface() }); + const out = await c.stats("v1"); + expect(out.viewCount).toBe("100"); + }); +}); diff --git a/_ts_packages/packages/youtube-adapter/test/tools.test.ts b/_ts_packages/packages/youtube-adapter/test/tools.test.ts new file mode 100644 index 0000000..57917e9 --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/test/tools.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect, vi } from "vitest"; +import { YouTubeClient, type YouTubeSurface } from "../src/client.js"; +import { buildYouTubeTools } from "../src/tools.js"; + +function makeSurface(): YouTubeSurface { + return { + subscriptions: vi.fn(async () => []), + channelVideos: vi.fn(async () => []), + search: vi.fn(async () => [{ videoId: "v1", title: "T", channel: "C" }]), + stats: vi.fn(async () => ({ videoId: "v1", viewCount: "9" })), + }; +} + +describe("youtube tool surface", () => { + it("registers 5 tools", () => { + const c = new YouTubeClient({ apiKey: "k", surface: makeSurface(), transcriptFn: async () => [] }); + const names = buildYouTubeTools(c).map((t) => t.name); + expect(names).toEqual([ + "youtube_subscriptions", + "youtube_new_videos", + "youtube_search", + "youtube_transcript", + "youtube_video_stats", + ]); + }); + + it("youtube_subscriptions handles empty list", async () => { + const c = new YouTubeClient({ apiKey: "k", surface: makeSurface(), transcriptFn: async () => [] }); + const tool = buildYouTubeTools(c).find((t) => t.name === "youtube_subscriptions"); + const out = await tool!.handler({}); + expect(out).toBe("No results."); + }); + + it("youtube_search formats result line", async () => { + const c = new YouTubeClient({ apiKey: "k", surface: makeSurface(), transcriptFn: async () => [] }); + const tool = buildYouTubeTools(c).find((t) => t.name === "youtube_search"); + const out = await tool!.handler({ query: "rust" }); + expect(out).toContain("v1"); + expect(out).toContain("T"); + }); + + it("youtube_transcript joins lines with spaces", async () => { + const c = new YouTubeClient({ + apiKey: "k", + surface: makeSurface(), + transcriptFn: async () => [ + { text: "hello", offset: 0, duration: 1 }, + { text: "world", offset: 1, duration: 1 }, + ], + }); + const tool = buildYouTubeTools(c).find((t) => t.name === "youtube_transcript"); + const out = await tool!.handler({ video_id: "x" }); + expect(out).toBe("hello world"); + }); +}); diff --git a/_ts_packages/packages/youtube-adapter/test/types.test.ts b/_ts_packages/packages/youtube-adapter/test/types.test.ts new file mode 100644 index 0000000..7884377 --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/test/types.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from "vitest"; +import { SubscriptionsArgs, NewVideosArgs, SearchArgs, VideoIdArgs } from "../src/types.js"; + +describe("youtube schemas", () => { + it("SubscriptionsArgs defaults max to 25", () => { + const r = SubscriptionsArgs.safeParse({}); + expect(r.success).toBe(true); + if (r.success) expect(r.data.max).toBe(25); + }); + + it("SubscriptionsArgs rejects max > 50", () => { + const r = SubscriptionsArgs.safeParse({ max: 51 }); + expect(r.success).toBe(false); + }); + + it("NewVideosArgs requires channel_id", () => { + expect(NewVideosArgs.safeParse({}).success).toBe(false); + expect(NewVideosArgs.safeParse({ channel_id: "UC1" }).success).toBe(true); + }); + + it("SearchArgs rejects empty query", () => { + expect(SearchArgs.safeParse({ query: "" }).success).toBe(false); + }); + + it("VideoIdArgs requires non-empty id", () => { + expect(VideoIdArgs.safeParse({ video_id: "" }).success).toBe(false); + expect(VideoIdArgs.safeParse({ video_id: "abc" }).success).toBe(true); + }); +}); diff --git a/_ts_packages/packages/youtube-adapter/tsconfig.json b/_ts_packages/packages/youtube-adapter/tsconfig.json new file mode 100644 index 0000000..2c00eac --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "types": ["node"] + }, + "include": ["src/**/*"], + "exclude": ["dist", "node_modules", "test/**/*"] +} diff --git a/_ts_packages/packages/youtube-adapter/vitest.config.ts b/_ts_packages/packages/youtube-adapter/vitest.config.ts new file mode 100644 index 0000000..d9ebefd --- /dev/null +++ b/_ts_packages/packages/youtube-adapter/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + include: ["test/**/*.test.ts"], + environment: "node", + }, +}); diff --git a/_ts_packages/tsconfig.base.json b/_ts_packages/tsconfig.base.json new file mode 100644 index 0000000..b22475f --- /dev/null +++ b/_ts_packages/tsconfig.base.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2022"], + "strict": true, + "noImplicitAny": true, + "exactOptionalPropertyTypes": true, + "noUncheckedIndexedAccess": true, + "noImplicitOverride": true, + "noFallthroughCasesInSwitch": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "composite": true, + "incremental": true + } +}