diff --git a/.commitlintrc.json b/.commitlintrc.json index c30e5a9..383b5c2 100644 --- a/.commitlintrc.json +++ b/.commitlintrc.json @@ -1,3 +1,6 @@ { - "extends": ["@commitlint/config-conventional"] + "extends": ["@commitlint/config-conventional"], + "rules": { + "header-max-length": [0, "always", 100] + } } diff --git a/.env.example b/.env.example index cb3ed46..375c2c5 100644 --- a/.env.example +++ b/.env.example @@ -57,3 +57,9 @@ FRONTEND_URL=http://localhost:5173 # If unset, falls back to FRONTEND_URL. # Local dev: http://localhost:5173 Prod: https://app.your-domain.com,https://staging.your-domain.com TRUSTED_ORIGINS= + +# ── MCP SERVER ──────────────────────────────────────────────────────────── + +# API key used by the MCP server to authenticate against the REST API (ak_ prefix) +# Generate one in the web UI under Account → API Keys +MCP_API_KEY= diff --git a/README.md b/README.md index 6500105..b008532 100644 --- a/README.md +++ b/README.md @@ -91,6 +91,38 @@ Account management endpoints (`/v1/me/*`) use the session cookie set by [Better --- +## MCP integration (LLM → Anki) + +The MCP server lets any LLM (Claude Desktop, API clients) manage your decks and notes directly: + +``` +User: "Create flashcards from this discussion" +LLM: " → cards appear in Anki instantly" +``` + +**Claude Desktop setup** — add to `~/Library/Application Support/Claude/claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "anki-cloud": { + "command": "bun", + "args": ["run", "/path/to/anki-cloud/mcp/src/index.ts"], + "env": { + "API_URL": "http://localhost:3000", + "API_KEY": "ak_your_key_here" + } + } + } +} +``` + +Generate an API key in the web UI under **Account → API Keys**, replace the path and key, then restart Claude Desktop. + +Full setup guide, available tools, and Docker deployment: [docs/mcp-integration.md](docs/mcp-integration.md) + +--- + ## Connecting Anki Desktop In Anki: **Preferences → Syncing → Self-hosted sync server**, set the URL to: @@ -151,8 +183,9 @@ cd e2e && bun test ## Project structure ``` -api/ REST API — TypeScript / Hono on Bun (shared workspace with db/) +api/ REST API — TypeScript / Hono on Bun (shared workspace with db/ and mcp/) db/ Drizzle ORM + SQLite schema (@anki-cloud/db) +mcp/ MCP server — wraps REST API, 8 tools for LLM integration web/ Account management UI (Vite + React) (standalone — cd web && bun install) e2e/ End-to-end tests (standalone — cd e2e && bun install) docs/ Architecture decisions (ADRs) + narrative docs diff --git a/api/Dockerfile b/api/Dockerfile index e175e2f..cee5632 100644 --- a/api/Dockerfile +++ b/api/Dockerfile @@ -5,6 +5,7 @@ WORKDIR /app COPY package.json ./ COPY api/package.json ./api/ COPY db/package.json ./db/ +COPY mcp/package.json ./mcp/ COPY bun.lock ./ RUN bun install --frozen-lockfile diff --git a/bun.lock b/bun.lock index 84a74df..48f1adf 100644 --- a/bun.lock +++ b/bun.lock @@ -38,12 +38,26 @@ "typescript": "^5.8.3", }, }, + "mcp": { + "name": "@anki-cloud/mcp", + "version": "0.1.0", + "dependencies": { + "@modelcontextprotocol/sdk": "1.10.2", + "zod": "^3.24.2", + }, + "devDependencies": { + "@types/bun": "^1.2.10", + "typescript": "^5.8.3", + }, + }, }, "packages": { "@anki-cloud/api": ["@anki-cloud/api@workspace:api"], "@anki-cloud/db": ["@anki-cloud/db@workspace:db"], + "@anki-cloud/mcp": ["@anki-cloud/mcp@workspace:mcp"], + "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@7.3.4", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/@asteasolutions/zod-to-openapi/-/zod-to-openapi-7.3.4.tgz", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^3.20.2" } }, "sha512-/2rThQ5zPi9OzVwes6U7lK1+Yvug0iXu25olp7S0XsYmOqnyMfxH7gdSQjn/+DSOHRg7wnotwGJSyL+fBKdnEA=="], "@babel/code-frame": ["@babel/code-frame@7.29.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/@babel/code-frame/-/code-frame-7.29.0.tgz", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], @@ -164,6 +178,8 @@ "@hono/zod-validator": ["@hono/zod-validator@0.7.6", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/@hono/zod-validator/-/zod-validator-0.7.6.tgz", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.25.0 || ^4.0.0" } }, "sha512-Io1B6d011Gj1KknV4rXYz4le5+5EubcWEU/speUjuw9XMMIaP3n78yXLhjd2A3PXaXaUwEAluOiAyLqhBEJgsw=="], + "@modelcontextprotocol/sdk": ["@modelcontextprotocol/sdk@1.10.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/@modelcontextprotocol/sdk/-/sdk-1.10.2.tgz", { "dependencies": { "content-type": "^1.0.5", "cors": "^2.8.5", "cross-spawn": "^7.0.3", "eventsource": "^3.0.2", "express": "^5.0.1", "express-rate-limit": "^7.5.0", "pkce-challenge": "^5.0.0", "raw-body": "^3.0.0", "zod": "^3.23.8", "zod-to-json-schema": "^3.24.1" } }, "sha512-rb6AMp2DR4SN+kc6L1ta2NCpApyA9WYNx3CrTSZvGxq9wH71bRur+zRqPfg0vQ9mjywR7qZdX2RGHOPq3ss+tA=="], + "@noble/ciphers": ["@noble/ciphers@2.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/@noble/ciphers/-/ciphers-2.2.0.tgz", {}, "sha512-Z6pjIZ/8IJcCGzb2S/0Px5J81yij85xASuk1teLNeg75bfT07MV3a/O2Mtn1I2se43k3lkVEcFaR10N4cgQcZA=="], "@noble/hashes": ["@noble/hashes@2.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/@noble/hashes/-/hashes-2.2.0.tgz", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="], @@ -192,6 +208,8 @@ "JSONStream": ["JSONStream@1.3.5", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/JSONStream/-/JSONStream-1.3.5.tgz", { "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" }, "bin": { "JSONStream": "./bin.js" } }, "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ=="], + "accepts": ["accepts@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/accepts/-/accepts-2.0.0.tgz", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "ajv": ["ajv@8.18.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/ajv/-/ajv-8.18.0.tgz", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A=="], "ansi-regex": ["ansi-regex@5.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/ansi-regex/-/ansi-regex-5.0.1.tgz", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], @@ -208,10 +226,18 @@ "better-call": ["better-call@1.3.5", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/better-call/-/better-call-1.3.5.tgz", { "dependencies": { "@better-auth/utils": "^0.4.0", "@better-fetch/fetch": "^1.1.21", "rou3": "^0.7.12", "set-cookie-parser": "^3.0.1" }, "peerDependencies": { "zod": "^4.0.0" }, "optionalPeers": ["zod"] }, "sha512-kOFJkBP7utAQLEYrobZm3vkTH8mXq5GNgvjc5/XEST1ilVHaxXUXfeDeFlqoETMtyqS4+3/h4ONX2i++ebZrvA=="], + "body-parser": ["body-parser@2.2.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/body-parser/-/body-parser-2.2.2.tgz", { "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" } }, "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA=="], + "buffer-from": ["buffer-from@1.1.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/buffer-from/-/buffer-from-1.1.2.tgz", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="], "bun-types": ["bun-types@1.3.12", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/bun-types/-/bun-types-1.3.12.tgz", { "dependencies": { "@types/node": "*" } }, "sha512-HqOLj5PoFajAQciOMRiIZGNoKxDJSr6qigAttOX40vJuSp6DN/CxWp9s3C1Xwm4oH7ybueITwiaOcWXoYVoRkA=="], + "bytes": ["bytes@3.1.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/bytes/-/bytes-3.1.2.tgz", {}, "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/call-bound/-/call-bound-1.0.4.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + "callsites": ["callsites@3.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/callsites/-/callsites-3.1.0.tgz", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], "chalk": ["chalk@5.6.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/chalk/-/chalk-5.6.2.tgz", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="], @@ -224,68 +250,138 @@ "compare-func": ["compare-func@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/compare-func/-/compare-func-2.0.0.tgz", { "dependencies": { "array-ify": "^1.0.0", "dot-prop": "^5.1.0" } }, "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA=="], + "content-disposition": ["content-disposition@1.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/content-disposition/-/content-disposition-1.1.0.tgz", {}, "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g=="], + + "content-type": ["content-type@1.0.5", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/content-type/-/content-type-1.0.5.tgz", {}, "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA=="], + "conventional-changelog-angular": ["conventional-changelog-angular@7.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/conventional-changelog-angular/-/conventional-changelog-angular-7.0.0.tgz", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-ROjNchA9LgfNMTTFSIWPzebCwOGFdgkEq45EnvvrmSLvCtAw0HSmrCs7/ty+wAeYUZyNay0YMUNYFTRL72PkBQ=="], "conventional-changelog-conventionalcommits": ["conventional-changelog-conventionalcommits@7.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/conventional-changelog-conventionalcommits/-/conventional-changelog-conventionalcommits-7.0.2.tgz", { "dependencies": { "compare-func": "^2.0.0" } }, "sha512-NKXYmMR/Hr1DevQegFB4MwfM5Vv0m4UIxKZTTYuD98lpTknaZlSRrDOG4X7wIXpGkfsYxZTghUN+Qq+T0YQI7w=="], "conventional-commits-parser": ["conventional-commits-parser@5.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/conventional-commits-parser/-/conventional-commits-parser-5.0.0.tgz", { "dependencies": { "JSONStream": "^1.3.5", "is-text-path": "^2.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "conventional-commits-parser": "cli.mjs" } }, "sha512-ZPMl0ZJbw74iS9LuX9YIAiW8pfM5p3yh2o/NbXHbkFuZzY5jvdi5jFycEOkmBW5H5I7nA+D6f3UcsCLP2vvSEA=="], + "cookie": ["cookie@0.7.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/cookie/-/cookie-0.7.2.tgz", {}, "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w=="], + + "cookie-signature": ["cookie-signature@1.2.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/cookie-signature/-/cookie-signature-1.2.2.tgz", {}, "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg=="], + + "cors": ["cors@2.8.6", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/cors/-/cors-2.8.6.tgz", { "dependencies": { "object-assign": "^4", "vary": "^1" } }, "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw=="], + "cosmiconfig": ["cosmiconfig@9.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/cosmiconfig/-/cosmiconfig-9.0.1.tgz", { "dependencies": { "env-paths": "^2.2.1", "import-fresh": "^3.3.0", "js-yaml": "^4.1.0", "parse-json": "^5.2.0" }, "peerDependencies": { "typescript": ">=4.9.5" }, "optionalPeers": ["typescript"] }, "sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ=="], "cosmiconfig-typescript-loader": ["cosmiconfig-typescript-loader@6.3.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-6.3.0.tgz", { "dependencies": { "jiti": "2.6.1" }, "peerDependencies": { "@types/node": "*", "cosmiconfig": ">=9", "typescript": ">=5" } }, "sha512-Akr82WH1Wfqatyiqpj8HDkO2o2KmJRu1FhKfSNJP3K4IdXwHfEyL7MOb62i1AGQVLtIQM+iCE9CGOtrfhR+mmA=="], + "cross-spawn": ["cross-spawn@7.0.6", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/cross-spawn/-/cross-spawn-7.0.6.tgz", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "dargs": ["dargs@8.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/dargs/-/dargs-8.1.0.tgz", {}, "sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw=="], + "debug": ["debug@4.4.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/debug/-/debug-4.4.3.tgz", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + "defu": ["defu@6.1.7", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/defu/-/defu-6.1.7.tgz", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="], + "depd": ["depd@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/depd/-/depd-2.0.0.tgz", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dot-prop": ["dot-prop@5.3.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/dot-prop/-/dot-prop-5.3.0.tgz", { "dependencies": { "is-obj": "^2.0.0" } }, "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q=="], "drizzle-kit": ["drizzle-kit@0.31.10", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/drizzle-kit/-/drizzle-kit-0.31.10.tgz", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.25.4", "tsx": "^4.21.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-7OZcmQUrdGI+DUNNsKBn1aW8qSoKuTH7d0mYgSP8bAzdFzKoovxEFnoGQp2dVs82EOJeYycqRtciopszwUf8bw=="], "drizzle-orm": ["drizzle-orm@0.43.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/drizzle-orm/-/drizzle-orm-0.43.1.tgz", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1.13", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-dUcDaZtE/zN4RV/xqGrVSMpnEczxd5cIaoDeor7Zst9wOe/HzC/7eAaulywWGYXdDEc9oBPMjayVEDg0ziTLJA=="], + "dunder-proto": ["dunder-proto@1.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/dunder-proto/-/dunder-proto-1.0.1.tgz", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "ee-first": ["ee-first@1.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/ee-first/-/ee-first-1.1.1.tgz", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], + "emoji-regex": ["emoji-regex@8.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/emoji-regex/-/emoji-regex-8.0.0.tgz", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + "encodeurl": ["encodeurl@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/encodeurl/-/encodeurl-2.0.0.tgz", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "env-paths": ["env-paths@2.2.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/env-paths/-/env-paths-2.2.1.tgz", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], "error-ex": ["error-ex@1.3.4", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/error-ex/-/error-ex-1.3.4.tgz", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ=="], + "es-define-property": ["es-define-property@1.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/es-define-property/-/es-define-property-1.0.1.tgz", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/es-errors/-/es-errors-1.3.0.tgz", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/es-object-atoms/-/es-object-atoms-1.1.1.tgz", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + "esbuild": ["esbuild@0.25.12", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/esbuild/-/esbuild-0.25.12.tgz", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "escalade": ["escalade@3.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/escalade/-/escalade-3.2.0.tgz", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + "escape-html": ["escape-html@1.0.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/escape-html/-/escape-html-1.0.3.tgz", {}, "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="], + + "etag": ["etag@1.8.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/etag/-/etag-1.8.1.tgz", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="], + + "eventsource": ["eventsource@3.0.7", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/eventsource/-/eventsource-3.0.7.tgz", { "dependencies": { "eventsource-parser": "^3.0.1" } }, "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA=="], + + "eventsource-parser": ["eventsource-parser@3.0.8", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/eventsource-parser/-/eventsource-parser-3.0.8.tgz", {}, "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ=="], + + "express": ["express@5.2.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/express/-/express-5.2.1.tgz", { "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" } }, "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw=="], + + "express-rate-limit": ["express-rate-limit@7.5.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/express-rate-limit/-/express-rate-limit-7.5.1.tgz", { "peerDependencies": { "express": ">= 4.11" } }, "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw=="], + "fast-deep-equal": ["fast-deep-equal@3.1.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-uri": ["fast-uri@3.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/fast-uri/-/fast-uri-3.1.0.tgz", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], + "finalhandler": ["finalhandler@2.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/finalhandler/-/finalhandler-2.1.1.tgz", { "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" } }, "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA=="], + "find-up": ["find-up@7.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/find-up/-/find-up-7.0.0.tgz", { "dependencies": { "locate-path": "^7.2.0", "path-exists": "^5.0.0", "unicorn-magic": "^0.1.0" } }, "sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g=="], + "forwarded": ["forwarded@0.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/forwarded/-/forwarded-0.2.0.tgz", {}, "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow=="], + + "fresh": ["fresh@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/fresh/-/fresh-2.0.0.tgz", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="], + "fsevents": ["fsevents@2.3.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/fsevents/-/fsevents-2.3.3.tgz", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + "function-bind": ["function-bind@1.1.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/function-bind/-/function-bind-1.1.2.tgz", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + "get-caller-file": ["get-caller-file@2.0.5", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/get-caller-file/-/get-caller-file-2.0.5.tgz", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + "get-intrinsic": ["get-intrinsic@1.3.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/get-intrinsic/-/get-intrinsic-1.3.0.tgz", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/get-proto/-/get-proto-1.0.1.tgz", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + "get-tsconfig": ["get-tsconfig@4.14.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/get-tsconfig/-/get-tsconfig-4.14.0.tgz", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA=="], "git-raw-commits": ["git-raw-commits@4.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/git-raw-commits/-/git-raw-commits-4.0.0.tgz", { "dependencies": { "dargs": "^8.0.0", "meow": "^12.0.1", "split2": "^4.0.0" }, "bin": { "git-raw-commits": "cli.mjs" } }, "sha512-ICsMM1Wk8xSGMowkOmPrzo2Fgmfo4bMHLNX6ytHjajRJUqvHOw/TFapQ+QG75c3X/tTDDhOSRPGC52dDbNM8FQ=="], "global-directory": ["global-directory@4.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/global-directory/-/global-directory-4.0.1.tgz", { "dependencies": { "ini": "4.1.1" } }, "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q=="], + "gopd": ["gopd@1.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/gopd/-/gopd-1.2.0.tgz", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "has-symbols": ["has-symbols@1.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/has-symbols/-/has-symbols-1.1.0.tgz", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "hasown": ["hasown@2.0.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/hasown/-/hasown-2.0.3.tgz", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg=="], + "hono": ["hono@4.12.14", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/hono/-/hono-4.12.14.tgz", {}, "sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w=="], + "http-errors": ["http-errors@2.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/http-errors/-/http-errors-2.0.1.tgz", { "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", "setprototypeof": "~1.2.0", "statuses": "~2.0.2", "toidentifier": "~1.0.1" } }, "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ=="], + + "iconv-lite": ["iconv-lite@0.7.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/iconv-lite/-/iconv-lite-0.7.2.tgz", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], + "import-fresh": ["import-fresh@3.3.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/import-fresh/-/import-fresh-3.3.1.tgz", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], "import-meta-resolve": ["import-meta-resolve@4.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="], + "inherits": ["inherits@2.0.4", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/inherits/-/inherits-2.0.4.tgz", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "ini": ["ini@4.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/ini/-/ini-4.1.1.tgz", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], + "ipaddr.js": ["ipaddr.js@1.9.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/ipaddr.js/-/ipaddr.js-1.9.1.tgz", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], + "is-arrayish": ["is-arrayish@0.2.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/is-arrayish/-/is-arrayish-0.2.1.tgz", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-obj": ["is-obj@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/is-obj/-/is-obj-2.0.0.tgz", {}, "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w=="], + "is-promise": ["is-promise@4.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/is-promise/-/is-promise-4.0.0.tgz", {}, "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="], + "is-text-path": ["is-text-path@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/is-text-path/-/is-text-path-2.0.0.tgz", { "dependencies": { "text-extensions": "^2.0.0" } }, "sha512-+oDTluR6WEjdXEJMnC2z6A4FRwFoYuvShVVEGsS7ewc0UTi2QtAKMDJuL4BDEVt+5T7MjFo12RP8ghOM75oKJw=="], + "isexe": ["isexe@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/isexe/-/isexe-2.0.0.tgz", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "jiti": ["jiti@2.6.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/jiti/-/jiti-2.6.1.tgz", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jose": ["jose@6.2.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/jose/-/jose-6.2.2.tgz", {}, "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ=="], @@ -324,12 +420,34 @@ "lodash.upperfirst": ["lodash.upperfirst@4.3.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz", {}, "sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/math-intrinsics/-/math-intrinsics-1.1.0.tgz", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "media-typer": ["media-typer@1.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/media-typer/-/media-typer-1.1.0.tgz", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], + "meow": ["meow@12.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/meow/-/meow-12.1.1.tgz", {}, "sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw=="], + "merge-descriptors": ["merge-descriptors@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/merge-descriptors/-/merge-descriptors-2.0.0.tgz", {}, "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g=="], + + "mime-db": ["mime-db@1.54.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/mime-db/-/mime-db-1.54.0.tgz", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="], + + "mime-types": ["mime-types@3.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/mime-types/-/mime-types-3.0.2.tgz", { "dependencies": { "mime-db": "^1.54.0" } }, "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A=="], + "minimist": ["minimist@1.2.8", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/minimist/-/minimist-1.2.8.tgz", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + "ms": ["ms@2.1.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/ms/-/ms-2.1.3.tgz", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + "nanostores": ["nanostores@1.3.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/nanostores/-/nanostores-1.3.0.tgz", {}, "sha512-XPUa/jz+P1oJvN9VBxw4L9MtdFfaH3DAryqPssqhb2kXjmb9npz0dly6rCsgFWOPr4Yg9mTfM3MDZgZZ+7A3lA=="], + "negotiator": ["negotiator@1.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/negotiator/-/negotiator-1.0.0.tgz", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], + + "object-assign": ["object-assign@4.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/object-assign/-/object-assign-4.1.1.tgz", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="], + + "object-inspect": ["object-inspect@1.13.4", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/object-inspect/-/object-inspect-1.13.4.tgz", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "on-finished": ["on-finished@2.4.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/on-finished/-/on-finished-2.4.1.tgz", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], + + "once": ["once@1.4.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/once/-/once-1.4.0.tgz", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + "openapi3-ts": ["openapi3-ts@4.5.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/openapi3-ts/-/openapi3-ts-4.5.0.tgz", { "dependencies": { "yaml": "^2.8.0" } }, "sha512-jaL+HgTq2Gj5jRcfdutgRGLosCy/hT8sQf6VOy+P+g36cZOjI1iukdPnijC+4CmeRzg/jEllJUboEic2FhxhtQ=="], "p-limit": ["p-limit@4.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/p-limit/-/p-limit-4.0.0.tgz", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], @@ -340,10 +458,26 @@ "parse-json": ["parse-json@5.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/parse-json/-/parse-json-5.2.0.tgz", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="], + "parseurl": ["parseurl@1.3.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/parseurl/-/parseurl-1.3.3.tgz", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], + "path-exists": ["path-exists@5.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/path-exists/-/path-exists-5.0.0.tgz", {}, "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ=="], + "path-key": ["path-key@3.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/path-key/-/path-key-3.1.1.tgz", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "path-to-regexp": ["path-to-regexp@8.4.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/path-to-regexp/-/path-to-regexp-8.4.2.tgz", {}, "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA=="], + "picocolors": ["picocolors@1.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/picocolors/-/picocolors-1.1.1.tgz", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + "pkce-challenge": ["pkce-challenge@5.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/pkce-challenge/-/pkce-challenge-5.0.1.tgz", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="], + + "proxy-addr": ["proxy-addr@2.0.7", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/proxy-addr/-/proxy-addr-2.0.7.tgz", { "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" } }, "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg=="], + + "qs": ["qs@6.15.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/qs/-/qs-6.15.1.tgz", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg=="], + + "range-parser": ["range-parser@1.2.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/range-parser/-/range-parser-1.2.1.tgz", {}, "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg=="], + + "raw-body": ["raw-body@3.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/raw-body/-/raw-body-3.0.2.tgz", { "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" } }, "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA=="], + "require-directory": ["require-directory@2.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/require-directory/-/require-directory-2.1.1.tgz", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], "require-from-string": ["require-from-string@2.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/require-from-string/-/require-from-string-2.0.2.tgz", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], @@ -354,16 +488,40 @@ "rou3": ["rou3@0.7.12", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/rou3/-/rou3-0.7.12.tgz", {}, "sha512-iFE4hLDuloSWcD7mjdCDhx2bKcIsYbtOTpfH5MHHLSKMOUyjqQXTeZVa289uuwEGEKFoE/BAPbhaU4B774nceg=="], + "router": ["router@2.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/router/-/router-2.2.0.tgz", { "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", "is-promise": "^4.0.0", "parseurl": "^1.3.3", "path-to-regexp": "^8.0.0" } }, "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/safer-buffer/-/safer-buffer-2.1.2.tgz", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + "semver": ["semver@7.7.4", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/semver/-/semver-7.7.4.tgz", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + "send": ["send@1.2.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/send/-/send-1.2.1.tgz", { "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" } }, "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ=="], + + "serve-static": ["serve-static@2.2.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/serve-static/-/serve-static-2.2.1.tgz", { "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", "parseurl": "^1.3.3", "send": "^1.2.0" } }, "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw=="], + "set-cookie-parser": ["set-cookie-parser@3.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/set-cookie-parser/-/set-cookie-parser-3.1.0.tgz", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="], + "setprototypeof": ["setprototypeof@1.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/setprototypeof/-/setprototypeof-1.2.0.tgz", {}, "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="], + + "shebang-command": ["shebang-command@2.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/shebang-command/-/shebang-command-2.0.0.tgz", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/shebang-regex/-/shebang-regex-3.0.0.tgz", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "side-channel": ["side-channel@1.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/side-channel/-/side-channel-1.1.0.tgz", { "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" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/side-channel-list/-/side-channel-list-1.0.1.tgz", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" } }, "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w=="], + + "side-channel-map": ["side-channel-map@1.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/side-channel-map/-/side-channel-map-1.0.1.tgz", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", { "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" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + "source-map": ["source-map@0.6.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/source-map/-/source-map-0.6.1.tgz", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="], "source-map-support": ["source-map-support@0.5.21", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/source-map-support/-/source-map-support-0.5.21.tgz", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="], "split2": ["split2@4.2.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/split2/-/split2-4.2.0.tgz", {}, "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg=="], + "statuses": ["statuses@2.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/statuses/-/statuses-2.0.2.tgz", {}, "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw=="], + "string-width": ["string-width@4.2.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/string-width/-/string-width-4.2.3.tgz", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "strip-ansi": ["strip-ansi@6.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/strip-ansi/-/strip-ansi-6.0.1.tgz", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], @@ -374,16 +532,28 @@ "tinyexec": ["tinyexec@1.1.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/tinyexec/-/tinyexec-1.1.1.tgz", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="], + "toidentifier": ["toidentifier@1.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/toidentifier/-/toidentifier-1.0.1.tgz", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], + "tsx": ["tsx@4.21.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/tsx/-/tsx-4.21.0.tgz", { "dependencies": { "esbuild": "~0.27.0", "get-tsconfig": "^4.7.5" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": { "tsx": "dist/cli.mjs" } }, "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw=="], + "type-is": ["type-is@2.0.1", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/type-is/-/type-is-2.0.1.tgz", { "dependencies": { "content-type": "^1.0.5", "media-typer": "^1.1.0", "mime-types": "^3.0.0" } }, "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw=="], + "typescript": ["typescript@5.9.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/typescript/-/typescript-5.9.3.tgz", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.19.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/undici-types/-/undici-types-7.19.2.tgz", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], "unicorn-magic": ["unicorn-magic@0.1.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/unicorn-magic/-/unicorn-magic-0.1.0.tgz", {}, "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ=="], + "unpipe": ["unpipe@1.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/unpipe/-/unpipe-1.0.0.tgz", {}, "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="], + + "vary": ["vary@1.1.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/vary/-/vary-1.1.2.tgz", {}, "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg=="], + + "which": ["which@2.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/which/-/which-2.0.2.tgz", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + "wrap-ansi": ["wrap-ansi@7.0.0", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/wrap-ansi/-/wrap-ansi-7.0.0.tgz", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + "wrappy": ["wrappy@1.0.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/wrappy/-/wrappy-1.0.2.tgz", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "y18n": ["y18n@5.0.8", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/y18n/-/y18n-5.0.8.tgz", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], "yaml": ["yaml@2.8.3", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/yaml/-/yaml-2.8.3.tgz", { "bin": { "yaml": "bin.mjs" } }, "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg=="], @@ -396,6 +566,8 @@ "zod": ["zod@3.25.76", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/zod/-/zod-3.25.76.tgz", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], + "zod-to-json-schema": ["zod-to-json-schema@3.25.2", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", { "peerDependencies": { "zod": "^3.25.28 || ^4" } }, "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA=="], + "@better-auth/core/zod": ["zod@4.3.6", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/zod/-/zod-4.3.6.tgz", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "https://cplace.jfrog.io/artifactory/api/npm/cplace-npm/esbuild/-/esbuild-0.18.20.tgz", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="], diff --git a/docker-compose.yml b/docker-compose.yml index f94d3ff..196826c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -39,5 +39,17 @@ services: depends_on: - anki-sync-server + mcp: + build: + context: . + dockerfile: mcp/Dockerfile + ports: + - "3001:3001" + environment: + API_URL: http://api:3000 + API_KEY: ${MCP_API_KEY} + depends_on: + - api + volumes: app-data: diff --git a/docs/images/claude-anki-cloud-integration-example--list-all-my-decks.png b/docs/images/claude-anki-cloud-integration-example--list-all-my-decks.png new file mode 100644 index 0000000..eeba8be Binary files /dev/null and b/docs/images/claude-anki-cloud-integration-example--list-all-my-decks.png differ diff --git a/docs/mcp-integration.md b/docs/mcp-integration.md new file mode 100644 index 0000000..24b673d --- /dev/null +++ b/docs/mcp-integration.md @@ -0,0 +1,317 @@ +# MCP Integration + +The anki-cloud MCP server exposes 8 tools to LLMs, enabling seamless LLM-to-Anki workflows. +Any MCP-compatible client (Claude Desktop, API clients) can create decks, add flashcards, +search notes, and more — without the user having Anki open. + +## Prerequisites + +1. A running anki-cloud instance (local or hosted) — see [SELF_HOSTING.md](SELF_HOSTING.md) +2. [Bun](https://bun.sh) installed locally +3. An API key — generate one in the web UI under **Account → API Keys** + +## Claude Desktop Setup + +Claude Desktop spawns the MCP server as a child process over stdio. Each user configures +their own key — the server runs locally on their machine. + +Add the following to your Claude Desktop config file: + +- **Mac:** `~/Library/Application Support/Claude/claude_desktop_config.json` +- **Windows:** `%APPDATA%\Claude\claude_desktop_config.json` + +```json +{ + "mcpServers": { + "anki-cloud": { + "command": "bun", + "args": [ + "run", + "/absolute/path/to/anki-cloud/mcp/src/index.ts" + ], + "env": { + "API_URL": "http://localhost:3000", + "API_KEY": "ak_your_key_here" + } + } + } +} +``` + +Replace `/absolute/path/to/anki-cloud` with the actual path to your clone and +`ak_your_key_here` with your API key. For a hosted instance, set `API_URL` to +`https://your-anki-cloud-instance.com`. + +Restart Claude Desktop after saving. The "anki-cloud" server should appear in the +tools panel (hammer icon). + +**Verify:** Ask Claude _"List my Anki decks"_ — it calls `list_decks` and shows results. + +## Environment Variables + +| Variable | Default | Description | +|-----------|-------------------------|-------------------------------------| +| `API_URL` | `http://localhost:3000` | Base URL of the anki-cloud REST API | +| `API_KEY` | _(required)_ | API key with `ak_` prefix | + +--- + +## Tools Reference + +### `list_decks` + +List all decks in the user's collection. + +**Inputs** + +| Parameter | Type | Required | Description | +|-----------|--------|----------|------------------------------------------| +| `limit` | number | no | Max results (1–1000, default 100) | +| `cursor` | string | no | Pagination cursor from previous response | + +**Example prompt** +> "List all my Anki decks." + +**Example response** + +![](images/claude-anki-cloud-integration-example--list-all-my-decks.png) + +--- + +### `get_deck` + +Get details of a specific deck by ID. + +**Inputs** + +| Parameter | Type | Required | Description | +|-----------|--------|----------|-----------------------------| +| `id` | string | yes | Deck ID (from `list_decks`) | + +**Example prompt** +> "Get details of deck 1700000000001." + +**Example response** + +```json +{ + "id": "1700000000001", + "name": "Spanish Vocabulary" +} +``` + +--- + +### `create_deck` + +Create a new deck. Use `::` for subdecks (e.g. `Languages::Spanish`). + +**Inputs** + +| Parameter | Type | Required | Description | +|-----------|--------|----------|----------------------------------------------| +| `name` | string | yes | Deck name, e.g. `Python` or `CS::Algorithms` | + +**Example prompt** +> "Create a deck called 'Python'." + +**Example response** + +```json +{ + "id": "1700000000042", + "name": "Python" +} +``` + +--- + +### `create_note` + +Add a single flashcard to a deck. The default Anki note type uses `Front` and `Back` fields. + +**Inputs** + +| Parameter | Type | Required | Description | +|--------------|------------------|----------|--------------------------------------| +| `deckId` | string | yes | Target deck ID | +| `fields` | object | yes | Map of field name → value | +| `tags` | array of strings | no | Tags to apply | +| `noteTypeId` | string | no | Note type ID (omit for deck default) | + +**Example prompt** +> "Add a flashcard to my Python deck: Front = 'What is a list comprehension?', Back = 'A concise way to create lists: [expr for item in iterable]'." + +**Example response** + +```json +{ + "id": "1700000000123" +} +``` + +--- + +### `create_notes_bulk` + +Add multiple flashcards in a single request. Prefer this over repeated `create_note` calls. + +**Inputs** + +| Parameter | Type | Required | Description | +|----------------------|--------|----------|-------------------------------| +| `deckId` | string | yes | Target deck ID | +| `notes` | array | yes | Array of note objects (min 1) | +| `notes[].fields` | object | yes | Field map for each note | +| `notes[].tags` | array | no | Tags for each note | +| `notes[].noteTypeId` | string | no | Note type for each note | + +**Example prompt** +> "Add 3 flashcards about the water cycle to my Science deck." + +**Example response** + +```json +{ + "ids": [ + "1700000000124", + "1700000000125", + "1700000000126" + ] +} +``` + +--- + +### `search_notes` + +Search notes using [Anki search syntax](https://docs.ankiweb.net/searching.html). + +**Inputs** + +| Parameter | Type | Required | Description | +|-----------|--------|----------|-----------------------------------| +| `query` | string | yes | Anki search query | +| `limit` | number | no | Max results (1–1000, default 100) | +| `cursor` | string | no | Pagination cursor | + +**Common query examples** + +| Query | Finds | +|----------------|--------------------------------------| +| `tag:python` | Notes tagged "python" | +| `deck:Spanish` | Notes in the Spanish deck | +| `added:7` | Notes added in the last 7 days | +| `front:hola` | Notes with "hola" in the Front field | + +**Example prompt** +> "Search my notes for tag:python." + +**Example response** + +```json +{ + "notes": [ + { + "id": "1700000000123", + "noteTypeId": "1", + "tags": [ + "python" + ], + "fields": { + "Front": "What is a decorator?", + "Back": "A function that wraps another function." + } + } + ], + "nextCursor": null +} +``` + +--- + +### `update_note` + +Edit a note's fields and/or tags. **All fields are replaced** — include all fields, not just changed ones. + +**Inputs** + +| Parameter | Type | Required | Description | +|-----------|------------------|----------|----------------------------------------------| +| `id` | string | yes | Note ID to update | +| `fields` | object | yes | Complete new field map (replaces all fields) | +| `tags` | array of strings | no | New tag list (replaces all existing tags) | + +**Example prompt** +> "Update note 1700000000123: change the Back field to 'A function that modifies another function, often using @syntax'." + +**Example response** + +```json +{ + "ok": true +} +``` + +--- + +### `delete_note` + +Permanently delete a note and all cards generated from it. Cannot be undone. + +**Inputs** + +| Parameter | Type | Required | Description | +|-----------|--------|----------|-------------------| +| `id` | string | yes | Note ID to delete | + +**Example prompt** +> "Delete note 1700000000123." + +**Example response** + +```json +{ + "ok": true +} +``` + +--- + +## Example Workflows + +**Create a deck and populate it from a conversation:** +> "We just discussed Python decorators. Create a deck called 'Python' and add 5 flashcards +> covering the key concepts from our discussion." + +**Bulk import from structured content:** +> "Here's a vocabulary list. Add all of these to my Spanish deck as Front/Back flashcards: ..." + +**Review and update existing notes:** +> "Search my notes for tag:python and show me the ones about decorators. Update the Back field +> of each one to include a code example." + +--- + +## Troubleshooting + +**"Server disconnected" in Claude Desktop** + +- Check the `args` path is absolute and correct: `ls /your/path/to/anki-cloud/mcp/src/index.ts` +- Check `bun` is on PATH: `which bun` in a terminal +- Check the API is running: `curl http://localhost:3000/health` + +**"INVALID_API_KEY" errors** + +- Verify the key starts with `ak_` and was copied in full +- Keys are shown only once on creation — generate a new one if lost + +**Tool calls succeed but no notes appear in Anki** + +- Sync Anki Desktop against the server: **Sync** button in Anki +- Verify Google Drive is connected in the web UI under **Account → Storage** + +**Claude doesn't show the anki-cloud tools** + +- Fully quit and relaunch Claude Desktop (menu bar → Quit, not just close window) +- Check the config file is valid JSON: `cat ~/Library/Application\ Support/Claude/claude_desktop_config.json | python3 -m json.tool` diff --git a/mcp/Dockerfile b/mcp/Dockerfile new file mode 100644 index 0000000..266a07b --- /dev/null +++ b/mcp/Dockerfile @@ -0,0 +1,17 @@ +FROM oven/bun:1 AS base +WORKDIR /app + +# Copy workspace manifests first for layer caching +COPY package.json ./ +COPY api/package.json ./api/ +COPY db/package.json ./db/ +COPY mcp/package.json ./mcp/ +COPY bun.lock ./ + +RUN bun install --frozen-lockfile + +# Copy source +COPY mcp/src ./mcp/src +COPY mcp/tsconfig.json ./mcp/ + +CMD ["bun", "run", "mcp/src/index.ts"] diff --git a/mcp/package.json b/mcp/package.json new file mode 100644 index 0000000..64e416e --- /dev/null +++ b/mcp/package.json @@ -0,0 +1,19 @@ +{ + "name": "@anki-cloud/mcp", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "bun run --watch src/index.ts", + "start": "bun run src/index.ts", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@modelcontextprotocol/sdk": "1.10.2", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/bun": "^1.2.10", + "typescript": "^5.8.3" + } +} diff --git a/mcp/src/client.ts b/mcp/src/client.ts new file mode 100644 index 0000000..dfac570 --- /dev/null +++ b/mcp/src/client.ts @@ -0,0 +1,99 @@ +// Copyright 2026 Archont Soft Daniel Klimuntowski +// Licensed under the Elastic License 2.0 — see LICENSE in the repository root. + +const API_URL = process.env["API_URL"] ?? "http://localhost:3000"; +const API_KEY = process.env["API_KEY"] ?? ""; + +async function apiRequest(path: string, options?: RequestInit): Promise { + const res = await fetch(`${API_URL}${path}`, { + ...options, + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${API_KEY}`, + ...((options?.headers as Record | undefined) ?? {}), + }, + }); + if (!res.ok) { + const body = await res.json().catch(() => ({ error: "api error" })); + throw new Error((body as { error?: string }).error ?? `HTTP ${res.status}`); + } + return res.json() as Promise; +} + +export interface Deck { + id: string; + name: string; +} + +export interface Note { + id: string; + noteTypeId: string; + tags: string[]; + fields: Record; +} + +export interface Paginated { + items: T[]; + nextCursor: string | null; +} + +type Pagination = { limit?: number; cursor?: string }; + +function buildQs(params: Record): string { + const p = new URLSearchParams(); + for (const [k, v] of Object.entries(params)) { + if (v !== undefined) p.set(k, String(v)); + } + const s = p.toString(); + return s ? `?${s}` : ""; +} + +export const listDecks = (opts?: Pagination) => + apiRequest<{ decks: Deck[]; nextCursor: string | null }>( + `/v1/decks${buildQs({ limit: opts?.limit, cursor: opts?.cursor })}` + ); + +export const getDeck = (id: string) => apiRequest(`/v1/decks/${id}`); + +export const createDeck = (name: string) => + apiRequest("/v1/decks", { method: "POST", body: JSON.stringify({ name }) }); + +export const listNotes = (deckId: string, opts?: Pagination) => + apiRequest<{ notes: Note[]; nextCursor: string | null }>( + `/v1/decks/${deckId}/notes${buildQs({ limit: opts?.limit, cursor: opts?.cursor })}` + ); + +export const createNote = ( + deckId: string, + body: { fields: Record; tags?: string[]; noteTypeId?: string } +) => + apiRequest<{ id: string }>(`/v1/decks/${deckId}/notes`, { + method: "POST", + body: JSON.stringify(body), + }); + +export const createNotesBulk = ( + deckId: string, + notes: { fields: Record; tags?: string[]; noteTypeId?: string }[] +) => + apiRequest<{ ids: string[] }>("/v1/cards/bulk", { + method: "POST", + body: JSON.stringify({ deckId, notes }), + }); + +export const searchNotes = (q: string, opts?: Pagination) => + apiRequest<{ notes: Note[]; nextCursor: string | null }>( + `/v1/notes/search${buildQs({ q, limit: opts?.limit, cursor: opts?.cursor })}` + ); + +export const updateNote = ( + id: string, + body: { fields: Record; tags?: string[] } +) => + apiRequest<{ ok: boolean }>(`/v1/notes/${id}`, { + method: "PUT", + body: JSON.stringify(body), + }); + +export const deleteNote = (id: string) => + apiRequest<{ ok: boolean }>(`/v1/notes/${id}`, { method: "DELETE" }); diff --git a/mcp/src/index.ts b/mcp/src/index.ts new file mode 100644 index 0000000..a98cced --- /dev/null +++ b/mcp/src/index.ts @@ -0,0 +1,9 @@ +// Copyright 2026 Archont Soft Daniel Klimuntowski +// Licensed under the Elastic License 2.0 — see LICENSE in the repository root. +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { registerTools } from "./tools/index.js"; + +const server = new McpServer({ name: "anki-cloud", version: "0.1.0" }); +registerTools(server); +await server.connect(new StdioServerTransport()); diff --git a/mcp/src/tools/decks.ts b/mcp/src/tools/decks.ts new file mode 100644 index 0000000..c736101 --- /dev/null +++ b/mcp/src/tools/decks.ts @@ -0,0 +1,59 @@ +// Copyright 2026 Archont Soft Daniel Klimuntowski +// Licensed under the Elastic License 2.0 — see LICENSE in the repository root. +import { z } from "zod"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import * as api from "../client.js"; + +export function registerDeckTools(server: McpServer): void { + server.tool( + "list_decks", + "List all Anki decks in the user's collection. Returns deck IDs and names. " + + "Use this to discover available decks before creating notes.", + { + limit: z + .number() + .int() + .min(1) + .max(1000) + .optional() + .describe("Maximum number of decks to return (default 100)"), + cursor: z + .string() + .optional() + .describe("Pagination cursor from a previous list_decks response"), + }, + async ({ limit, cursor }) => { + const data = await api.listDecks({ limit, cursor }); + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; + } + ); + + server.tool( + "get_deck", + "Get details of a specific Anki deck by its ID.", + { + id: z.string().describe("The deck ID (e.g. from list_decks)"), + }, + async ({ id }) => { + const deck = await api.getDeck(id); + return { content: [{ type: "text", text: JSON.stringify(deck, null, 2) }] }; + } + ); + + server.tool( + "create_deck", + "Create a new Anki deck. Returns the new deck's ID and name.", + { + name: z + .string() + .min(1) + .describe( + "Name for the new deck, e.g. 'Spanish Vocabulary' or 'Physics::Optics' (use :: for subdecks)" + ), + }, + async ({ name }) => { + const deck = await api.createDeck(name); + return { content: [{ type: "text", text: JSON.stringify(deck, null, 2) }] }; + } + ); +} diff --git a/mcp/src/tools/index.ts b/mcp/src/tools/index.ts new file mode 100644 index 0000000..be485ef --- /dev/null +++ b/mcp/src/tools/index.ts @@ -0,0 +1,10 @@ +// Copyright 2026 Archont Soft Daniel Klimuntowski +// Licensed under the Elastic License 2.0 — see LICENSE in the repository root. +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { registerDeckTools } from "./decks.js"; +import { registerNoteTools } from "./notes.js"; + +export function registerTools(server: McpServer): void { + registerDeckTools(server); + registerNoteTools(server); +} diff --git a/mcp/src/tools/notes.ts b/mcp/src/tools/notes.ts new file mode 100644 index 0000000..dc08429 --- /dev/null +++ b/mcp/src/tools/notes.ts @@ -0,0 +1,117 @@ +// Copyright 2026 Archont Soft Daniel Klimuntowski +// Licensed under the Elastic License 2.0 — see LICENSE in the repository root. +import { z } from "zod"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import * as api from "../client.js"; + +const NoteFieldsSchema = z.record(z.string()).describe( + "Map of field name to value. The default Anki note type uses 'Front' and 'Back'. " + + "Use search_notes to inspect existing notes and discover field names." +); + +export function registerNoteTools(server: McpServer): void { + server.tool( + "create_note", + "Add a single flashcard to an Anki deck. " + + "The fields map must match the note type's field names — the default type uses 'Front' and 'Back'. " + + "Returns the new note ID.", + { + deckId: z.string().describe("ID of the deck to add the note to"), + fields: NoteFieldsSchema, + tags: z + .array(z.string()) + .optional() + .describe("Optional list of tags, e.g. ['python', 'chapter3']"), + noteTypeId: z + .string() + .optional() + .describe("Note type ID — omit to use the deck's default note type"), + }, + async ({ deckId, fields, tags, noteTypeId }) => { + const result = await api.createNote(deckId, { fields, tags, noteTypeId }); + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } + ); + + server.tool( + "create_notes_bulk", + "Add multiple flashcards to an Anki deck in a single request. " + + "More efficient than calling create_note repeatedly when adding many cards. " + + "Returns an array of created note IDs in the same order as the input.", + { + deckId: z.string().describe("ID of the deck to add the notes to"), + notes: z + .array( + z.object({ + fields: NoteFieldsSchema, + tags: z.array(z.string()).optional(), + noteTypeId: z.string().optional(), + }) + ) + .min(1) + .describe("Array of notes to create"), + }, + async ({ deckId, notes }) => { + const result = await api.createNotesBulk(deckId, notes); + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } + ); + + server.tool( + "search_notes", + "Search for notes using Anki's search syntax. " + + "Examples: 'tag:python', 'deck:Spanish front:hola', 'added:7' (added in last 7 days). " + + "Returns matching notes with their fields and tags.", + { + query: z + .string() + .min(1) + .describe("Anki search query string, e.g. 'tag:python' or 'deck:MyDeck'"), + limit: z + .number() + .int() + .min(1) + .max(1000) + .optional() + .describe("Maximum number of notes to return (default 100)"), + cursor: z + .string() + .optional() + .describe("Pagination cursor from a previous search_notes response"), + }, + async ({ query, limit, cursor }) => { + const data = await api.searchNotes(query, { limit, cursor }); + return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; + } + ); + + server.tool( + "update_note", + "Edit an existing note's fields and/or tags. All fields are replaced, not merged. " + + "Use search_notes to find the note ID and its current fields first.", + { + id: z.string().describe("The note ID to update"), + fields: NoteFieldsSchema, + tags: z + .array(z.string()) + .optional() + .describe("New tag list — replaces existing tags entirely"), + }, + async ({ id, fields, tags }) => { + const result = await api.updateNote(id, { fields, tags }); + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } + ); + + server.tool( + "delete_note", + "Permanently delete a note and all cards generated from it. This cannot be undone.", + { + id: z.string().describe("The note ID to delete"), + }, + async ({ id }) => { + const result = await api.deleteNote(id); + return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] }; + } + ); +} diff --git a/mcp/tsconfig.json b/mcp/tsconfig.json new file mode 100644 index 0000000..6e1ccf2 --- /dev/null +++ b/mcp/tsconfig.json @@ -0,0 +1,14 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "noUncheckedIndexedAccess": true, + "lib": ["ES2022"], + "types": ["bun"], + "skipLibCheck": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules"] +} diff --git a/package.json b/package.json index fc795d4..c0ac111 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "anki-cloud", "version": "0.1.0", "private": true, - "workspaces": ["api", "db"], + "workspaces": ["api", "db", "mcp"], "devDependencies": { "@commitlint/cli": "^19.8.0", "@commitlint/config-conventional": "^19.8.0"