From 844b68f1bc799e921fd7be0e54ef2ddf28bba18b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Mar 2026 18:45:30 +0000 Subject: [PATCH 1/9] chore(deps): bump express-rate-limit from 8.2.1 to 8.3.0 Bumps [express-rate-limit](https://github.com/express-rate-limit/express-rate-limit) from 8.2.1 to 8.3.0. - [Release notes](https://github.com/express-rate-limit/express-rate-limit/releases) - [Commits](https://github.com/express-rate-limit/express-rate-limit/compare/v8.2.1...v8.3.0) --- updated-dependencies: - dependency-name: express-rate-limit dependency-version: 8.3.0 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index b1768f8..3d26062 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1821,7 +1821,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2665,7 +2664,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -2705,12 +2703,12 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.0.tgz", + "integrity": "sha512-KJzBawY6fB9FiZGdE/0aftepZ91YlaGIrV8vgblRM3J8X+dHx/aiowJWwkx6LIGyuqGiANsjSwwrbb8mifOJ4Q==", "license": "MIT", "dependencies": { - "ip-address": "10.0.1" + "ip-address": "10.1.0" }, "engines": { "node": ">= 16" @@ -3041,7 +3039,6 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz", "integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.9.0" } @@ -3089,9 +3086,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "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" @@ -3567,8 +3564,7 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/p-limit": { "version": "3.1.0", @@ -3771,7 +3767,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -3784,7 +3779,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4533,7 +4527,6 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4912,7 +4905,6 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } From e11b80f8c2a4457af63882c8e979f55c01551ccc Mon Sep 17 00:00:00 2001 From: andrew-paystack Date: Fri, 13 Mar 2026 14:01:01 +0300 Subject: [PATCH 2/9] chore: update release workflow to OIDC --- .github/workflows/release.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3541b3..3a907d0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,7 @@ jobs: build: runs-on: ubuntu-latest permissions: + id-token: write contents: read steps: - uses: actions/checkout@v5 @@ -14,7 +15,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: '20.x' + node-version: '24.x' registry-url: 'https://registry.npmjs.org' - name: Install dependencies @@ -27,6 +28,4 @@ jobs: run: npm run build - name: Publish to npmjs - run: npm publish --access public - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} \ No newline at end of file + run: npm publish --access public \ No newline at end of file From 82a592a08bbffbbd7cbcc70d749b6820c8a0a622 Mon Sep 17 00:00:00 2001 From: andrew-paystack Date: Fri, 13 Mar 2026 17:34:05 +0300 Subject: [PATCH 3/9] initial setup of skills --- README.md | 4 +++- src/resources/index.ts | 2 ++ src/server.ts | 25 +++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ef27483..cfb2e9b 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ The Paystack MCP Server exposes the **entire Paystack API** to AI assistants by | Resource | URI | Description | | ------------------------- | ---------------------------- | -------------------------------------------------------- | | `paystack_operation_list` | `paystack://operations/list` | List all available Paystack operations and their details | +| `paystack_skill` | `paystack://skill` | Paystack developer knowledge: docs index, code snippet links, payment channel reference | ### Example @@ -135,12 +136,13 @@ When you ask your AI assistant something like _"Get me the last 5 transactions o ### Prompt recommendation -To get the best results when using this MCP server, be specific in your prompts and always include "Paystack" in your requests. This helps the LLM quickly identify and use the appropriate Paystack tools. +To get the best results when using this MCP server, be specific in your prompts and always include "Paystack" in your requests. The server provides built-in instructions and a knowledge resource (`paystack://skill`) that help the AI assistant find the right documentation, code snippets, and API details. **Good prompts:** - "Initialize a Paystack transaction for 50000 NGN" - "Create a customer with email user@example.com on my Paystack account" - "How can I send money with the Paystack API?" +- "Show me a cURL example for verifying a Paystack transaction" **Less effective prompts:** - "List my transactions" (unclear which service to use) diff --git a/src/resources/index.ts b/src/resources/index.ts index 53467f0..6629170 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -1,10 +1,12 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { OpenAPIParser } from "../openapi-parser"; import { registerOperationListResource } from "./paystack-operation-list"; +import { registerSkillResource } from "./paystack-skill"; export function registerAllResources( server: McpServer, openapi: OpenAPIParser ) { registerOperationListResource(server, openapi); + registerSkillResource(server); } diff --git a/src/server.ts b/src/server.ts index 65abadf..40e10b7 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,10 +5,35 @@ import { OpenAPIParser } from "./openapi-parser"; import { registerAllTools } from "./tools"; import { registerAllResources } from "./resources"; +const SERVER_INSTRUCTIONS = `You are connected to the Paystack MCP Server, which provides access to the full Paystack API. + +## Source Priority +1. Use this server's tools and resources as the primary source of truth for API details: + - "paystack_operation_list" resource: browse all available API operations + - "get_paystack_operation" tool: get endpoint details (method, path, parameters, request body) + - "make_paystack_request" tool: execute API requests +2. For integration guides, best practices, and deeper context, refer to Paystack documentation at https://paystack.com/docs/llms.txt +3. For code snippets in JS/TS or cURL, refer to the "paystack_skill" resource for links +4. If information is not available from the above sources, say so clearly — do not invent Paystack-specific details + +## Critical Accuracy Rules +- All amounts must be in the smallest currency unit: kobo (NGN), pesewas (GHS), cents (ZAR/KES/USD). XOF has no subunit but amounts must still be multiplied by 100. +- API requests require authentication: secret keys (server-side) or public keys (client-side only for Popup/Mobile SDKs) +- This server only accepts test keys (sk_test_*). Never use live keys. +- Always verify transactions server-side before delivering value +- Validate webhook signatures using your secret key before processing events +- Supported currencies: NGN (Nigeria), GHS (Ghana), ZAR (South Africa), KES (Kenya), USD (International) + +## Workflow +Always call "get_paystack_operation" to get endpoint details before calling "make_paystack_request". Do not guess endpoint paths, methods, or parameter names. +`; + async function createServer(cliApiKey?: string) { const server = new McpServer({ name: "paystack", version: "0.0.1", + }, { + instructions: SERVER_INSTRUCTIONS, }); const oasPath = path.join(__dirname, "./", "data/paystack.openapi.yaml"); From da37e23866dab6abc40d56b6024ae46efd295fc2 Mon Sep 17 00:00:00 2001 From: andrew-paystack Date: Mon, 16 Mar 2026 22:24:07 +0300 Subject: [PATCH 4/9] add Paystack skills --- .gitignore | 3 +- .vscode/mcp.json | 6 +- README.md | 1 - src/data/paystack-skill.md | 38 ++++++++ src/resources/paystack-skill.ts | 37 ++++++++ src/server.ts | 1 - test/paystack-skill-resource.spec.ts | 124 +++++++++++++++++++++++++++ 7 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 src/data/paystack-skill.md create mode 100644 src/resources/paystack-skill.ts create mode 100644 test/paystack-skill-resource.spec.ts diff --git a/.gitignore b/.gitignore index 76ae0f2..d2361ef 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,5 @@ node_modules/ !.env.example # Stores VSCode versions used for testing VSCode extensions -.vscode-test \ No newline at end of file +.vscode-test +.vscode \ No newline at end of file diff --git a/.vscode/mcp.json b/.vscode/mcp.json index d62c2d2..49181b9 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -2,7 +2,7 @@ "servers": { "test-mcp-server": { "type": "stdio", - "command": "node", + "command": "/Users/andrew/.nvm/versions/node/v22.12.0/bin/node", "args": [ "build/index.js" ], @@ -13,7 +13,9 @@ "type": "node" } }, - "env": {} + "env": { + "PAYSTACK_TEST_SECRET_KEY": "sk_test_23bb8447ce48d687dfbd9017afcc63321ef1cde9" + } } } } \ No newline at end of file diff --git a/README.md b/README.md index cfb2e9b..f0473dd 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,6 @@ The Paystack MCP Server exposes the **entire Paystack API** to AI assistants by | Resource | URI | Description | | ------------------------- | ---------------------------- | -------------------------------------------------------- | | `paystack_operation_list` | `paystack://operations/list` | List all available Paystack operations and their details | -| `paystack_skill` | `paystack://skill` | Paystack developer knowledge: docs index, code snippet links, payment channel reference | ### Example diff --git a/src/data/paystack-skill.md b/src/data/paystack-skill.md new file mode 100644 index 0000000..745f928 --- /dev/null +++ b/src/data/paystack-skill.md @@ -0,0 +1,38 @@ +# Paystack Developer Knowledge + +## Documentation + +For integration guides, best practices, and detailed documentation, use the Paystack LLM-friendly docs index: +https://paystack.com/docs/llms.txt + +This index covers payments, transfers, terminal, guides, libraries, API reference, and API changelog. + +## Code Snippets + +Code snippets for Paystack API endpoints and integration guides are maintained in the PaystackOSS/doc-code-snippets repository. Snippets are available in JavaScript (Node.js), Shell/cURL, and PHP. + +### Browsing Snippets +- API Reference snippets: https://github.com/PaystackOSS/doc-code-snippets/tree/main/src/api +- Documentation snippets: https://github.com/PaystackOSS/doc-code-snippets/tree/main/src/doc + +### Fetching a Specific Snippet +Once you know the exact path from browsing, fetch the raw content at: +https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/src/api/{topic}/{action}/index.{js,sh} + +For example: +- https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/src/api/transactions/initialize/index.js +- https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/src/api/transactions/initialize/index.sh + +### Snippet Notes +- Snippets use placeholder values like "SECRET_KEY" or "YOUR_SECRET_KEY" — replace with actual test keys +- Not every endpoint has a snippet; if unavailable, construct the request from the operation details provided by the "get_paystack_operation" tool + +## Payment Channels by Country + +| Country | Currencies | Payment Channels | +|---------|----------|-----------------| +| Nigeria | NGN, USD | Cards (Visa, Mastercard, Verve, Amex), Bank Transfer, USSD, QR Code, Apple Pay | +| Ghana | GHS | Cards (Visa, Mastercard, Verve), Mobile Money (MTN, AirtelTigo, Telecel), Bank Transfer, QR Code | +| South Africa | ZAR | Cards (Visa, Mastercard, Verve, Amex), Apple Pay, QR Code | +| Kenya | KES, USD | Cards (Visa, Mastercard, Verve), Mobile Money (M-PESA, Airtel Money), Bank Transfers (Pesalink) +| Côte d'Ivoire | XOF | Cards (Visa, Mastercard, Verve), Mobile Money (MTN MoMo, Wave, Orange Money), Apple Pay | \ No newline at end of file diff --git a/src/resources/paystack-skill.ts b/src/resources/paystack-skill.ts new file mode 100644 index 0000000..160a6fb --- /dev/null +++ b/src/resources/paystack-skill.ts @@ -0,0 +1,37 @@ +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import * as fs from "node:fs"; +import * as path from "node:path"; + +// Load skill content from external markdown file +let SKILL_CONTENT: string; +try { + const skillPath = path.join(__dirname, "./", "data/paystack-skill.md"); + SKILL_CONTENT = fs.readFileSync(skillPath, "utf-8"); +} catch (error) { + console.error("Error loading paystack-skill.md:", error); + SKILL_CONTENT = "# Paystack Developer Knowledge\n\nError: Could not load skill content from file."; +} + +export function registerSkillResource(server: McpServer) { + server.registerResource( + "paystack_skill", + "paystack://skill", + { + description: + "Paystack developer knowledge: docs index pointer, code snippet URL patterns, and payment channel reference", + title: "Paystack Developer Knowledge", + mimeType: "text/markdown", + }, + async (uri) => { + return { + contents: [ + { + uri: uri.href, + text: SKILL_CONTENT, + mimeType: "text/markdown", + }, + ], + }; + } + ); +} diff --git a/src/server.ts b/src/server.ts index 40e10b7..e8f3d99 100644 --- a/src/server.ts +++ b/src/server.ts @@ -22,7 +22,6 @@ const SERVER_INSTRUCTIONS = `You are connected to the Paystack MCP Server, which - This server only accepts test keys (sk_test_*). Never use live keys. - Always verify transactions server-side before delivering value - Validate webhook signatures using your secret key before processing events -- Supported currencies: NGN (Nigeria), GHS (Ghana), ZAR (South Africa), KES (Kenya), USD (International) ## Workflow Always call "get_paystack_operation" to get endpoint details before calling "make_paystack_request". Do not guess endpoint paths, methods, or parameter names. diff --git a/test/paystack-skill-resource.spec.ts b/test/paystack-skill-resource.spec.ts new file mode 100644 index 0000000..2928638 --- /dev/null +++ b/test/paystack-skill-resource.spec.ts @@ -0,0 +1,124 @@ +import assert from "node:assert"; +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { registerSkillResource } from "../src/resources/paystack-skill.js"; + +describe("PaystackSkillResource", () => { + let resourceHandler: any; + let registeredName: string; + let registeredUri: string; + let registeredMetadata: any; + + before(() => { + const server = { + registerResource: (name: string, uri: string, metadata: any, handler: any) => { + registeredName = name; + registeredUri = uri; + registeredMetadata = metadata; + resourceHandler = handler; + } + } as any; + + registerSkillResource(server); + }); + + describe("Registration", () => { + it("should register with the correct name", () => { + assert.strictEqual(registeredName, "paystack_skill"); + }); + + it("should register with the correct URI", () => { + assert.strictEqual(registeredUri, "paystack://skill"); + }); + + it("should set mimeType to text/markdown", () => { + assert.strictEqual(registeredMetadata.mimeType, "text/markdown"); + }); + + it("should have a description", () => { + assert.ok(registeredMetadata.description); + assert.ok(registeredMetadata.description.length > 0); + }); + }); + + describe("Content", () => { + let content: string; + + before(async () => { + const mockUri = new URL("paystack://skill"); + const result = await resourceHandler(mockUri); + content = result.contents[0].text; + }); + + it("should return text/markdown mimeType in response", async () => { + const mockUri = new URL("paystack://skill"); + const result = await resourceHandler(mockUri); + assert.strictEqual(result.contents[0].mimeType, "text/markdown"); + }); + + it("should include documentation index section", () => { + assert.ok(content.includes("## Documentation Index")); + }); + + it("should include code snippets section", () => { + assert.ok(content.includes("## Code Snippets")); + }); + + it("should include payment channels by country section", () => { + assert.ok(content.includes("## Payment Channels by Country")); + }); + + it("should include links to Paystack docs", () => { + assert.ok(content.includes("https://paystack.com/docs/payments/")); + assert.ok(content.includes("https://paystack.com/docs/transfers/")); + assert.ok(content.includes("https://paystack.com/docs/terminal/")); + assert.ok(content.includes("https://paystack.com/docs/api/")); + }); + + it("should include snippet repo URL pattern for JS", () => { + assert.ok(content.includes("PaystackOSS/doc-code-snippets")); + assert.ok(content.includes("index.js")); + }); + + it("should include snippet repo URL pattern for Shell", () => { + assert.ok(content.includes("index.sh")); + }); + + it("should include all supported countries", () => { + assert.ok(content.includes("Nigeria")); + assert.ok(content.includes("Ghana")); + assert.ok(content.includes("South Africa")); + assert.ok(content.includes("Kenya")); + assert.ok(content.includes("Côte d'Ivoire")); + }); + }); +}); + +describe("ServerInstructions", () => { + it("should pass instructions to the McpServer constructor", async () => { + // Dynamically import server module to verify instructions are set + // We check the source directly since McpServer options are private + const fs = await import("node:fs"); + const path = await import("node:path"); + const serverSource = fs.readFileSync( + path.join(__dirname, "../src/server.ts"), + "utf-8" + ); + + assert.ok( + serverSource.includes("instructions:"), + "server.ts should pass instructions to McpServer" + ); + assert.ok( + serverSource.includes("smallest currency unit"), + "instructions should include currency unit rule" + ); + assert.ok( + serverSource.includes("get_paystack_operation"), + "instructions should reference the operation tool" + ); + assert.ok( + serverSource.includes("do not invent"), + "instructions should include anti-hallucination directive" + ); + }); +}); From 1e9ff421d2379d192796fc89b6281c036d8068a9 Mon Sep 17 00:00:00 2001 From: andrew-paystack Date: Mon, 16 Mar 2026 22:40:40 +0300 Subject: [PATCH 5/9] fix skill file reading --- src/resources/paystack-skill.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/paystack-skill.ts b/src/resources/paystack-skill.ts index 160a6fb..e0c8e29 100644 --- a/src/resources/paystack-skill.ts +++ b/src/resources/paystack-skill.ts @@ -5,7 +5,7 @@ import * as path from "node:path"; // Load skill content from external markdown file let SKILL_CONTENT: string; try { - const skillPath = path.join(__dirname, "./", "data/paystack-skill.md"); + const skillPath = path.join(__dirname, "..", "data", "paystack-skill.md"); SKILL_CONTENT = fs.readFileSync(skillPath, "utf-8"); } catch (error) { console.error("Error loading paystack-skill.md:", error); From 8f0489025f6ef9df60b511658b4a024243024d3e Mon Sep 17 00:00:00 2001 From: andrew-paystack Date: Mon, 16 Mar 2026 22:53:20 +0300 Subject: [PATCH 6/9] fix tests and skill file --- src/data/paystack-skill.md | 2 +- test/paystack-skill-resource.spec.ts | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/data/paystack-skill.md b/src/data/paystack-skill.md index 745f928..b36c07a 100644 --- a/src/data/paystack-skill.md +++ b/src/data/paystack-skill.md @@ -1,6 +1,6 @@ # Paystack Developer Knowledge -## Documentation +## Documentation Index For integration guides, best practices, and detailed documentation, use the Paystack LLM-friendly docs index: https://paystack.com/docs/llms.txt diff --git a/test/paystack-skill-resource.spec.ts b/test/paystack-skill-resource.spec.ts index 2928638..57464c8 100644 --- a/test/paystack-skill-resource.spec.ts +++ b/test/paystack-skill-resource.spec.ts @@ -68,10 +68,7 @@ describe("PaystackSkillResource", () => { }); it("should include links to Paystack docs", () => { - assert.ok(content.includes("https://paystack.com/docs/payments/")); - assert.ok(content.includes("https://paystack.com/docs/transfers/")); - assert.ok(content.includes("https://paystack.com/docs/terminal/")); - assert.ok(content.includes("https://paystack.com/docs/api/")); + assert.ok(content.includes("https://paystack.com/docs/llms.txt")); }); it("should include snippet repo URL pattern for JS", () => { From 3e7cfbcb051026e44fb67d97510ebb7fb64811d5 Mon Sep 17 00:00:00 2001 From: andrew-paystack Date: Thu, 26 Mar 2026 17:10:43 +0300 Subject: [PATCH 7/9] chore: clean up skills --- src/data/paystack-skill.md | 8 ++++++-- src/resources/index.ts | 5 +++-- src/resources/paystack-skill.ts | 16 ++-------------- src/server.ts | 9 +++++++-- test/paystack-skill-resource.spec.ts | 9 ++++++++- 5 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/data/paystack-skill.md b/src/data/paystack-skill.md index b36c07a..8b44246 100644 --- a/src/data/paystack-skill.md +++ b/src/data/paystack-skill.md @@ -1,4 +1,7 @@ -# Paystack Developer Knowledge +--- +name: paystack-skill +description: Guide users through using the Paystack's products and APIs. Use this when users ask for sample code, guides, example usage, references, webhook implementation or similar structured content. Trigger when you're not certain based on information from existing resources. +--- ## Documentation Index @@ -26,6 +29,7 @@ For example: ### Snippet Notes - Snippets use placeholder values like "SECRET_KEY" or "YOUR_SECRET_KEY" — replace with actual test keys - Not every endpoint has a snippet; if unavailable, construct the request from the operation details provided by the "get_paystack_operation" tool +- The supported webhook events are in https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/dist/doc/payments/webhooks/events.js ## Payment Channels by Country @@ -33,6 +37,6 @@ For example: |---------|----------|-----------------| | Nigeria | NGN, USD | Cards (Visa, Mastercard, Verve, Amex), Bank Transfer, USSD, QR Code, Apple Pay | | Ghana | GHS | Cards (Visa, Mastercard, Verve), Mobile Money (MTN, AirtelTigo, Telecel), Bank Transfer, QR Code | -| South Africa | ZAR | Cards (Visa, Mastercard, Verve, Amex), Apple Pay, QR Code | +| South Africa | ZAR | Cards (Visa, Mastercard, Verve, Amex), Apple Pay, Scan to Pay, Capitec Pay, Ozow | | Kenya | KES, USD | Cards (Visa, Mastercard, Verve), Mobile Money (M-PESA, Airtel Money), Bank Transfers (Pesalink) | Côte d'Ivoire | XOF | Cards (Visa, Mastercard, Verve), Mobile Money (MTN MoMo, Wave, Orange Money), Apple Pay | \ No newline at end of file diff --git a/src/resources/index.ts b/src/resources/index.ts index 6629170..74dc2d6 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -5,8 +5,9 @@ import { registerSkillResource } from "./paystack-skill"; export function registerAllResources( server: McpServer, - openapi: OpenAPIParser + openapi: OpenAPIParser, + skillContent: string ) { registerOperationListResource(server, openapi); - registerSkillResource(server); + registerSkillResource(server, skillContent); } diff --git a/src/resources/paystack-skill.ts b/src/resources/paystack-skill.ts index e0c8e29..5f292de 100644 --- a/src/resources/paystack-skill.ts +++ b/src/resources/paystack-skill.ts @@ -1,18 +1,6 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import * as fs from "node:fs"; -import * as path from "node:path"; -// Load skill content from external markdown file -let SKILL_CONTENT: string; -try { - const skillPath = path.join(__dirname, "..", "data", "paystack-skill.md"); - SKILL_CONTENT = fs.readFileSync(skillPath, "utf-8"); -} catch (error) { - console.error("Error loading paystack-skill.md:", error); - SKILL_CONTENT = "# Paystack Developer Knowledge\n\nError: Could not load skill content from file."; -} - -export function registerSkillResource(server: McpServer) { +export function registerSkillResource(server: McpServer, skillContent: string) { server.registerResource( "paystack_skill", "paystack://skill", @@ -27,7 +15,7 @@ export function registerSkillResource(server: McpServer) { contents: [ { uri: uri.href, - text: SKILL_CONTENT, + text: skillContent, mimeType: "text/markdown", }, ], diff --git a/src/server.ts b/src/server.ts index e8f3d99..e3bed24 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,6 +4,7 @@ import path from "path"; import { OpenAPIParser } from "./openapi-parser"; import { registerAllTools } from "./tools"; import { registerAllResources } from "./resources"; +import { loadSkillContent } from "./skill-loader"; const SERVER_INSTRUCTIONS = `You are connected to the Paystack MCP Server, which provides access to the full Paystack API. @@ -13,13 +14,14 @@ const SERVER_INSTRUCTIONS = `You are connected to the Paystack MCP Server, which - "get_paystack_operation" tool: get endpoint details (method, path, parameters, request body) - "make_paystack_request" tool: execute API requests 2. For integration guides, best practices, and deeper context, refer to Paystack documentation at https://paystack.com/docs/llms.txt -3. For code snippets in JS/TS or cURL, refer to the "paystack_skill" resource for links +3. For code snippets in JS/TS or cURL, refer to the "paystack_skill" resource 4. If information is not available from the above sources, say so clearly — do not invent Paystack-specific details ## Critical Accuracy Rules - All amounts must be in the smallest currency unit: kobo (NGN), pesewas (GHS), cents (ZAR/KES/USD). XOF has no subunit but amounts must still be multiplied by 100. - API requests require authentication: secret keys (server-side) or public keys (client-side only for Popup/Mobile SDKs) - This server only accepts test keys (sk_test_*). Never use live keys. +- Prefer to use Webhooks for event-driven flows based on your paystack-skills file. - Always verify transactions server-side before delivering value - Validate webhook signatures using your secret key before processing events @@ -40,8 +42,11 @@ async function createServer(cliApiKey?: string) { await openapi.parse(); + const bundledSkillPath = path.join(__dirname, "data", "paystack-skill.md"); + const skillContent = await loadSkillContent(bundledSkillPath); + registerAllTools(server, openapi, cliApiKey); - registerAllResources(server, openapi); + registerAllResources(server, openapi, skillContent); return server; } diff --git a/test/paystack-skill-resource.spec.ts b/test/paystack-skill-resource.spec.ts index 57464c8..fc97316 100644 --- a/test/paystack-skill-resource.spec.ts +++ b/test/paystack-skill-resource.spec.ts @@ -1,7 +1,14 @@ import assert from "node:assert"; +import * as fs from "node:fs"; +import * as path from "node:path"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { registerSkillResource } from "../src/resources/paystack-skill.js"; +const SKILL_CONTENT = fs.readFileSync( + path.join(__dirname, "..", "src", "data", "paystack-skill.md"), + "utf-8" +); + describe("PaystackSkillResource", () => { let resourceHandler: any; let registeredName: string; @@ -18,7 +25,7 @@ describe("PaystackSkillResource", () => { } } as any; - registerSkillResource(server); + registerSkillResource(server, SKILL_CONTENT); }); describe("Registration", () => { From 145a20597fa00371a337ada143e4dac6891f67a9 Mon Sep 17 00:00:00 2001 From: andrew-paystack Date: Thu, 26 Mar 2026 18:43:18 +0300 Subject: [PATCH 8/9] add skill-loader --- src/skill-loader.ts | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 src/skill-loader.ts diff --git a/src/skill-loader.ts b/src/skill-loader.ts new file mode 100644 index 0000000..7b3d53b --- /dev/null +++ b/src/skill-loader.ts @@ -0,0 +1,49 @@ +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { homedir } from "node:os"; + +const DEFAULT_SKILL_URL = + "https://raw.githubusercontent.com/PaystackOSS/paystack-mcp-server/main/src/data/paystack-skill.md"; + +const CACHE_DIR = path.join(homedir(), ".paystack-mcp"); +const CACHE_PATH = path.join(CACHE_DIR, "skill-cache.md"); +const MAX_AGE_MS = 48 * 60 * 60 * 1000; // 48 hours + +export async function loadSkillContent(bundledPath: string): Promise { + const url = process.env.PAYSTACK_SKILL_URL || DEFAULT_SKILL_URL; + + try { + // Check disk cache freshness + const stat = await fs.stat(CACHE_PATH); + if (Date.now() - stat.mtimeMs < MAX_AGE_MS) { + return await fs.readFile(CACHE_PATH, "utf-8"); + } + } catch { + // Cache missing or unreadable — continue to fetch + } + + try { + const res = await fetch(url, { signal: AbortSignal.timeout(3000) }); + if (res.ok) { + const text = await res.text(); + if (text.includes("name: paystack")) { + try { + await fs.mkdir(CACHE_DIR, { recursive: true }); + await fs.writeFile(CACHE_PATH, text, "utf-8"); + } catch { + // Cache write failed — non-fatal + } + return text; + } + } + } catch { + // Fetch failed or timed out — fall through + } + + // Fallback: stale cache → bundled file + try { + return await fs.readFile(CACHE_PATH, "utf-8"); + } catch { + return await fs.readFile(bundledPath, "utf-8"); + } +} From 0b6c68b2070fc662b99e6b734e71b00d43fd8bb9 Mon Sep 17 00:00:00 2001 From: andrew-paystack Date: Thu, 16 Apr 2026 14:41:35 +0300 Subject: [PATCH 9/9] chore: add linting and clean up release steps --- .github/workflows/ci.yml | 32 + .github/workflows/release.yml | 14 +- .prettierrc | 6 + eslint.config.mjs | 18 + package-lock.json | 1394 +++++++++++++++++++++- package.json | 21 +- src/config.ts | 14 +- src/data/paystack-skill.md | 20 +- src/data/paystack.openapi.yaml | 2 +- src/index.ts | 21 +- src/logger.ts | 14 +- src/openapi-parser.ts | 131 +- src/paystack-client.ts | 34 +- src/resources/index.ts | 10 +- src/resources/paystack-operation-list.ts | 43 +- src/resources/paystack-skill.ts | 16 +- src/server.ts | 39 +- src/skill-loader.ts | 22 +- src/tools/get-paystack-operation.ts | 47 +- src/tools/index.ts | 14 +- src/tools/make-paystack-request.ts | 48 +- src/types/client.ts | 4 +- src/types/index.ts | 4 +- src/types/openapi-components.ts | 65 +- test/make-paystack-request-tool.spec.ts | 102 +- test/openapi-parser.spec.ts | 22 +- test/paystack-client.spec.ts | 72 +- test/paystack-skill-resource.spec.ts | 108 +- tsconfig.json | 2 +- 29 files changed, 1863 insertions(+), 476 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .prettierrc create mode 100644 eslint.config.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..6235fd6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + pull_request: + branches: [main] + +jobs: + ci: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20.x' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Check formatting + run: npm run format:check + + - name: Build + run: npm run build + + - name: Test + run: npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3541b3..3bb2ec6 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -19,13 +19,25 @@ jobs: - name: Install dependencies run: npm ci - + + - name: Lint + run: npm run lint + + - name: Check formatting + run: npm run format:check + + - name: Test + run: npm test + - name: Sync version with release tag run: npm version ${{ github.event.release.tag_name }} --no-git-tag-version - name: Build package run: npm run build + - name: Audit publish contents + run: npm pack --dry-run + - name: Publish to npmjs run: npm publish --access public env: diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..29f78a6 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,6 @@ +{ + "singleQuote": true, + "printWidth": 100, + "tabWidth": 2, + "trailingComma": "all" +} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..afe2542 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,18 @@ +import js from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import prettierConfig from 'eslint-config-prettier'; + +export default tseslint.config( + js.configs.recommended, + ...tseslint.configs.recommended, + prettierConfig, + { + ignores: ['build/**', 'node_modules/**'], + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + }, + }, +); diff --git a/package-lock.json b/package-lock.json index 4538ad8..0ef52ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "@paystack/mcp-server", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@paystack/mcp-server", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "dependencies": { - "@modelcontextprotocol/inspector": "^0.18.0", + "@apidevtools/swagger-parser": "^12.1.0", "@modelcontextprotocol/sdk": "^1.26.0", "dotenv": "^17.2.3", "zod": "^4.3.6" @@ -18,21 +18,28 @@ "paystack-mcp-server": "build/index.js" }, "devDependencies": { - "@apidevtools/swagger-parser": "^12.1.0", + "@eslint/js": "^9.0.0", + "@modelcontextprotocol/inspector": "^0.18.0", "@types/mocha": "^10.0.10", "@types/node": "^25.0.7", + "eslint": "^9.0.0", + "eslint-config-prettier": "^10.0.0", "js-yaml": "^4.1.1", "mocha": "^11.3.0", "openapi-types": "^12.1.3", + "prettier": "^3.0.0", "tsx": "^4.21.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "typescript-eslint": "^8.0.0" + }, + "engines": { + "node": ">=20.0.0" } }, "node_modules/@apidevtools/json-schema-ref-parser": { "version": "14.0.1", "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-14.0.1.tgz", "integrity": "sha512-Oc96zvmxx1fqoSEdUmfmvvb59/KDOnUoJ7s2t7bISyAn0XEz57LCCw8k2Y4Pf3mwKaZLMciESALORLgfe2frCw==", - "dev": true, "license": "MIT", "dependencies": { "@types/json-schema": "^7.0.15", @@ -49,7 +56,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -59,14 +65,12 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", - "dev": true, "license": "MIT" }, "node_modules/@apidevtools/swagger-parser": { "version": "12.1.0", "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-12.1.0.tgz", "integrity": "sha512-e5mJoswsnAX0jG+J09xHFYQXb/bUc5S3pLpMxUuRUA2H8T2kni3yEoyz2R3Dltw5f4A6j6rPNMpWTK+iVDFlng==", - "dev": true, "license": "MIT", "dependencies": { "@apidevtools/json-schema-ref-parser": "14.0.1", @@ -84,6 +88,7 @@ "version": "0.8.1", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.9" @@ -534,10 +539,227 @@ "node": ">=18" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@floating-ui/core": { "version": "1.7.4", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", + "dev": true, "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" @@ -547,6 +769,7 @@ "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", + "dev": true, "license": "MIT", "dependencies": { "@floating-ui/core": "^1.7.4", @@ -557,6 +780,7 @@ "version": "2.1.7", "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", + "dev": true, "license": "MIT", "dependencies": { "@floating-ui/dom": "^1.7.5" @@ -570,6 +794,7 @@ "version": "0.2.10", "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "dev": true, "license": "MIT" }, "node_modules/@hono/node-server": { @@ -584,6 +809,58 @@ "hono": "^4" } }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -606,6 +883,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -615,12 +893,14 @@ "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/@jridgewell/trace-mapping": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", @@ -631,6 +911,7 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector/-/inspector-0.18.0.tgz", "integrity": "sha512-aBrBDaI8MtvyS9j3TMRgTHZaOwbe/zh2rbIVplIBtxWifaSfvQX9DbnoI3xv9sZjgeFyF/3CwZdfEVTUx2RfBg==", + "dev": true, "license": "MIT", "workspaces": [ "client", @@ -661,6 +942,7 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector-cli/-/inspector-cli-0.18.0.tgz", "integrity": "sha512-QMPjKx8zKmX17S1LF2gWuwbYglKexkdgB0HhKZFXzGrQ0MYoKUsIgokMyV48xr4LipaLS3b2v3ut3nV/jhWeSg==", + "dev": true, "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.24.3", @@ -675,6 +957,7 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector-client/-/inspector-client-0.18.0.tgz", "integrity": "sha512-M6A5SN09tYCoTTGwMi5hdQpesX5eHYn3FCAHTWfv1pZ1Cy7h5GjB1LxL5WhbMhXWmFFJ6AnQVGAJj0P0XkVhUg==", + "dev": true, "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.24.3", @@ -711,6 +994,7 @@ "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", @@ -727,12 +1011,14 @@ "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, "license": "MIT" }, "node_modules/@modelcontextprotocol/inspector-client/node_modules/zod": { "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -742,6 +1028,7 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/inspector-server/-/inspector-server-0.18.0.tgz", "integrity": "sha512-N7mDwUuj+gB8ZbZ52M4Oqh37qChS8kWJUkc4qL/MMsaQTVshXEOTcyiQ/mLKa17O5uODZQerAnQJWZbZYReBkg==", + "dev": true, "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.24.3", @@ -760,6 +1047,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -769,6 +1057,7 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -838,18 +1127,21 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "dev": true, "license": "MIT" }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "dev": true, "license": "MIT" }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -873,6 +1165,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -903,6 +1196,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -929,6 +1223,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -947,6 +1242,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -962,6 +1258,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -977,6 +1274,7 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1013,6 +1311,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1031,6 +1330,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1046,6 +1346,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1073,6 +1374,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1088,6 +1390,7 @@ "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -1113,6 +1416,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.2.tgz", "integrity": "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g==", + "dev": true, "license": "MIT", "peerDependencies": { "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" @@ -1122,6 +1426,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1140,6 +1445,7 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.4" @@ -1163,6 +1469,7 @@ "version": "2.1.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.2.4" @@ -1186,6 +1493,7 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1223,6 +1531,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1241,6 +1550,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "dev": true, "license": "MIT", "dependencies": { "@floating-ui/react-dom": "^2.0.0", @@ -1273,6 +1583,7 @@ "version": "1.1.9", "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3", @@ -1297,6 +1608,7 @@ "version": "1.1.5", "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -1321,6 +1633,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.2.3" @@ -1344,6 +1657,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1362,6 +1676,7 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1393,6 +1708,7 @@ "version": "2.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/number": "1.1.1", @@ -1436,6 +1752,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1454,6 +1771,7 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1472,6 +1790,7 @@ "version": "1.2.6", "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1501,6 +1820,7 @@ "version": "1.1.13", "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1531,6 +1851,7 @@ "version": "1.2.15", "resolved": "https://registry.npmjs.org/@radix-ui/react-toast/-/react-toast-1.2.15.tgz", "integrity": "sha512-3OSz3TacUWy4WtOXV38DggwxoqJK4+eDkNMl5Z/MJZaoUPaP4/9lf81xXMe1I2ReTAptverZUpbPY4wWwWyL5g==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1565,6 +1886,7 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -1599,6 +1921,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -1617,6 +1940,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1632,6 +1956,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", @@ -1651,6 +1976,7 @@ "version": "0.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1669,6 +1995,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" @@ -1687,6 +2014,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1702,6 +2030,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "dev": true, "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -1717,6 +2046,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/rect": "1.1.1" @@ -1735,6 +2065,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -1753,6 +2084,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3" @@ -1776,37 +2108,48 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", "integrity": "sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node12": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true, "license": "MIT" }, "node_modules/@tsconfig/node16": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true, + "license": "MIT" + }, + "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/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/mocha": { @@ -1820,28 +2163,312 @@ "version": "25.2.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.0.tgz", "integrity": "sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==", + "dev": true, "license": "MIT", "dependencies": { "undici-types": "~7.16.0" } }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.58.2", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.2", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/acorn": { "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", "bin": { "acorn": "bin/acorn" @@ -1850,10 +2477,21 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { "version": "8.3.4", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, "license": "MIT", "dependencies": { "acorn": "^8.11.0" @@ -1882,7 +2520,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", "integrity": "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==", - "dev": true, "license": "MIT", "peerDependencies": { "ajv": "^8.5.0" @@ -1927,6 +2564,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -1942,19 +2580,20 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true, "license": "MIT" }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, "license": "Python-2.0" }, "node_modules/aria-hidden": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -1967,6 +2606,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, "license": "MIT" }, "node_modules/body-parser": { @@ -2014,6 +2654,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bundle-name/-/bundle-name-4.1.0.tgz", "integrity": "sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==", + "dev": true, "license": "MIT", "dependencies": { "run-applescript": "^7.0.0" @@ -2067,9 +2708,18 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", - "dev": true, "license": "MIT" }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -2087,6 +2737,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -2103,6 +2754,7 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -2131,6 +2783,7 @@ "version": "0.7.1", "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "dev": true, "license": "Apache-2.0", "dependencies": { "clsx": "^2.1.1" @@ -2143,6 +2796,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -2157,6 +2811,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -2166,12 +2821,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -2186,6 +2843,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -2198,6 +2856,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", @@ -2215,6 +2874,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2224,6 +2884,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/cmdk/-/cmdk-1.1.1.tgz", "integrity": "sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==", + "dev": true, "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "^1.1.1", @@ -2240,6 +2901,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -2252,12 +2914,14 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/commander": { "version": "13.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2267,12 +2931,14 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, "license": "MIT" }, "node_modules/concurrently": { "version": "9.2.1", "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "dev": true, "license": "MIT", "dependencies": { "chalk": "4.1.2", @@ -2354,6 +3020,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true, "license": "MIT" }, "node_modules/cross-spawn": { @@ -2374,6 +3041,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 12" @@ -2409,10 +3077,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/default-browser": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/default-browser/-/default-browser-5.5.0.tgz", "integrity": "sha512-H9LMLr5zwIbSxrmvikGuI/5KGhZ8E2zH3stkMgM5LpOWDutGM2JZaj460Udnf1a+946zc7YBgrqEWwbk7zHvGw==", + "dev": true, "license": "MIT", "dependencies": { "bundle-name": "^4.1.0", @@ -2429,6 +3105,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/default-browser-id/-/default-browser-id-5.0.1.tgz", "integrity": "sha512-x1VCxdX4t+8wVfd1so/9w+vQ4vx7lKd2Qp5tDRutErwmR85OgmfX7RlLRMWafRMY7hbEiXIbudNrjOAPa/hL8Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -2441,6 +3118,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz", "integrity": "sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -2462,6 +3140,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "dev": true, "license": "MIT" }, "node_modules/diff": { @@ -2605,6 +3284,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2629,6 +3309,224 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -2730,6 +3628,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, "license": "MIT" }, "node_modules/fast-uri": { @@ -2748,10 +3654,29 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/fetch-blob": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", + "dev": true, "funding": [ { "type": "github", @@ -2771,6 +3696,19 @@ "node": "^12.20 || >= 14.13" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -2809,15 +3747,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" - } + "license": "ISC" }, "node_modules/foreground-child": { "version": "3.3.1", @@ -2840,6 +3799,7 @@ "version": "4.0.10", "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", + "dev": true, "license": "MIT", "dependencies": { "fetch-blob": "^3.1.2" @@ -2894,6 +3854,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" @@ -2927,6 +3888,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2979,6 +3941,32 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2995,6 +3983,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3079,6 +4068,43 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3107,6 +4133,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-3.0.0.tgz", "integrity": "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==", + "dev": true, "license": "MIT", "bin": { "is-docker": "cli.js" @@ -3118,19 +4145,44 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-inside-container": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-inside-container/-/is-inside-container-1.0.0.tgz", "integrity": "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==", + "dev": true, "license": "MIT", "dependencies": { "is-docker": "^3.0.0" @@ -3188,6 +4240,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.0.tgz", "integrity": "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==", + "dev": true, "license": "MIT", "dependencies": { "is-inside-container": "^1.0.0" @@ -3234,13 +4287,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -3249,6 +4302,13 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -3261,6 +4321,37 @@ "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", "license": "BSD-2-Clause" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3277,6 +4368,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -3298,6 +4396,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -3317,6 +4416,7 @@ "version": "0.523.0", "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.523.0.tgz", "integrity": "sha512-rUjQoy7egZT9XYVXBK1je9ckBnNp7qzRZOhLQx5RcEp2dCGlXo+mv6vf7Am4LimEcFBJIIZzSGfgTqc9QCrPSw==", + "dev": true, "license": "ISC", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" @@ -3326,6 +4426,7 @@ "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true, "license": "ISC" }, "node_modules/math-intrinsics": { @@ -3452,6 +4553,13 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -3466,6 +4574,7 @@ "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", "deprecated": "Use your platform's native DOMException instead", + "dev": true, "funding": [ { "type": "github", @@ -3485,6 +4594,7 @@ "version": "3.3.2", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", + "dev": true, "license": "MIT", "dependencies": { "data-uri-to-buffer": "^4.0.0", @@ -3545,6 +4655,7 @@ "version": "10.2.0", "resolved": "https://registry.npmjs.org/open/-/open-10.2.0.tgz", "integrity": "sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==", + "dev": true, "license": "MIT", "dependencies": { "default-browser": "^5.2.1", @@ -3563,9 +4674,26 @@ "version": "12.1.3", "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", - "dev": true, "license": "MIT" }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -3605,6 +4733,19 @@ "dev": true, "license": "BlueOak-1.0.0" }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3628,6 +4769,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true, "license": "(WTFPL OR MIT)" }, "node_modules/path-key": { @@ -3673,19 +4815,60 @@ "dev": true, "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/pkce-challenge": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-4.1.0.tgz", "integrity": "sha512-ZBmhE1C9LcPoH9XZSdwiPtbPHZROwAnMy+kIFQVrnMCxY4Cudlz3gBOpzilgc0jOgRaiT3sIWfpMomW2ar2orQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=16.20.0" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/prismjs": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz", "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3708,6 +4891,7 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -3766,6 +4950,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -3778,6 +4963,7 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -3791,6 +4977,7 @@ "version": "2.7.2", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "dev": true, "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -3816,6 +5003,7 @@ "version": "2.3.8", "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "dev": true, "license": "MIT", "dependencies": { "react-style-singleton": "^2.2.2", @@ -3838,6 +5026,7 @@ "version": "0.14.1", "resolved": "https://registry.npmjs.org/react-simple-code-editor/-/react-simple-code-editor-0.14.1.tgz", "integrity": "sha512-BR5DtNRy+AswWJECyA17qhUDvrrCZ6zXOCfkQY5zSmb96BVUbpVAv03WpcjcwtCwiLbIANx3gebHOcXYn1EHow==", + "dev": true, "license": "MIT", "peerDependencies": { "react": ">=16.8.0", @@ -3848,6 +5037,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "dev": true, "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", @@ -3884,6 +5074,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -3898,6 +5089,16 @@ "node": ">=0.10.0" } }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/resolve-pkg-maps": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", @@ -3928,6 +5129,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/run-applescript/-/run-applescript-7.1.0.tgz", "integrity": "sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3940,6 +5142,7 @@ "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, "license": "Apache-2.0", "dependencies": { "tslib": "^2.1.0" @@ -3976,11 +5179,25 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" } }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", @@ -4021,6 +5238,7 @@ "version": "6.1.6", "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "dev": true, "license": "MIT", "dependencies": { "bytes": "3.0.0", @@ -4036,6 +5254,7 @@ "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -4046,6 +5265,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.8" @@ -4055,6 +5275,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4064,6 +5285,7 @@ "version": "1.33.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4073,6 +5295,7 @@ "version": "2.1.18", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "~1.33.0" @@ -4085,6 +5308,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -4097,12 +5321,14 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "dev": true, "license": "MIT" }, "node_modules/serve-handler/node_modules/range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -4158,6 +5384,7 @@ "version": "1.8.3", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4255,6 +5482,7 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-5.1.2.tgz", "integrity": "sha512-/y7tJKALVZ1lPzeZZB9jYnmtrL7d0N2zkorii5a7r7dhHkWIuLTzZpZzMJLK1dmYRgX/NCc4iarTO3F7BS2c/A==", + "dev": true, "license": "MIT", "dependencies": { "debug": "^4.3.7", @@ -4391,6 +5619,7 @@ "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -4406,12 +5635,30 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.1.tgz", "integrity": "sha512-Oo6tHdpZsGpkKG88HJ8RR1rg/RdnEkQEfMoEk2x1XRI3F1AxeU+ijRXpiVUF4UbLfcxxRGw6TbUINKYdWVsQTQ==", + "dev": true, "license": "MIT", "funding": { "type": "github", "url": "https://github.com/sponsors/dcastil" } }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -4425,15 +5672,30 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, "license": "MIT", "bin": { "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-node": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", @@ -4477,6 +5739,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.4.tgz", "integrity": "sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -4486,6 +5749,7 @@ "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, "license": "0BSD" }, "node_modules/tsx": { @@ -4508,6 +5772,19 @@ "fsevents": "~2.3.3" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -4526,6 +5803,7 @@ "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", @@ -4535,10 +5813,35 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/undici-types": { "version": "7.16.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, "license": "MIT" }, "node_modules/unpipe": { @@ -4554,6 +5857,7 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" @@ -4563,6 +5867,7 @@ "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "dev": true, "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -4584,6 +5889,7 @@ "version": "1.1.3", "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "dev": true, "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", @@ -4606,6 +5912,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true, "license": "MIT" }, "node_modules/vary": { @@ -4621,6 +5928,7 @@ "version": "3.3.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", + "dev": true, "license": "MIT", "engines": { "node": ">= 8" @@ -4641,6 +5949,16 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/workerpool": { "version": "9.3.4", "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", @@ -4753,6 +6071,7 @@ "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", + "dev": true, "license": "MIT", "engines": { "node": ">=10.0.0" @@ -4774,6 +6093,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/wsl-utils/-/wsl-utils-0.1.0.tgz", "integrity": "sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==", + "dev": true, "license": "MIT", "dependencies": { "is-wsl": "^3.1.0" @@ -4789,6 +6109,7 @@ "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -4798,6 +6119,7 @@ "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, "license": "MIT", "dependencies": { "cliui": "^8.0.1", @@ -4816,6 +6138,7 @@ "version": "21.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, "license": "ISC", "engines": { "node": ">=12" @@ -4841,6 +6164,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -4850,12 +6174,14 @@ "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -4870,6 +6196,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" @@ -4882,6 +6209,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" diff --git a/package.json b/package.json index 163b25f..8fcb4e4 100644 --- a/package.json +++ b/package.json @@ -14,9 +14,17 @@ "build": "tsc && cp -r src/data build/", "build:watch": "tsc --watch", "prepack": "npm run build", + "prepublishOnly": "npm run build && npm test", "dev": "tsx src/index.ts", "inspect": "set DANGEROUSLY_OMIT_AUTH=true && CLIENT_PORT=8090 SERVER_PORT=9000 npx @modelcontextprotocol/inspector npm run dev", - "test": "mocha" + "test": "mocha", + "lint": "eslint src/ test/", + "lint:fix": "eslint src/ test/ --fix", + "format:check": "prettier --check src/ test/", + "format": "prettier --write src/ test/" + }, + "engines": { + "node": ">=20.0.0" }, "files": [ "build", "README.md" ], "keywords": [ @@ -30,19 +38,24 @@ "author": "Andrew-Paystack", "license": "MIT", "dependencies": { - "@modelcontextprotocol/inspector": "^0.18.0", "@modelcontextprotocol/sdk": "^1.26.0", + "@apidevtools/swagger-parser": "^12.1.0", "dotenv": "^17.2.3", "zod": "^4.3.6" }, "devDependencies": { - "@apidevtools/swagger-parser": "^12.1.0", + "@eslint/js": "^9.0.0", + "@modelcontextprotocol/inspector": "^0.18.0", "@types/mocha": "^10.0.10", "@types/node": "^25.0.7", + "eslint": "^9.0.0", + "eslint-config-prettier": "^10.0.0", "js-yaml": "^4.1.1", "mocha": "^11.3.0", "openapi-types": "^12.1.3", + "prettier": "^3.0.0", "tsx": "^4.21.0", - "typescript": "^5.9.3" + "typescript": "^5.9.3", + "typescript-eslint": "^8.0.0" } } diff --git a/src/config.ts b/src/config.ts index 189c98e..2f6ae12 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,27 +1,29 @@ import dotenv from 'dotenv'; // Load environment variables from .env file -dotenv.config({quiet: true}); +dotenv.config({ quiet: true }); // Get configuration with optional CLI API key export function getConfig(cliApiKey?: string) { const apiKey = cliApiKey || process.env.PAYSTACK_TEST_SECRET_KEY; - + if (!apiKey) { console.error('Error: PAYSTACK_TEST_SECRET_KEY is required'); process.exit(1); } - + if (!apiKey.startsWith('sk_test_')) { - console.error('Error: PAYSTACK_TEST_SECRET_KEY must begin with "sk_test_". No live keys allowed.'); + console.error( + 'Error: PAYSTACK_TEST_SECRET_KEY must begin with "sk_test_". No live keys allowed.', + ); process.exit(1); } - + if (apiKey.length < 30) { console.error('Error: PAYSTACK_TEST_SECRET_KEY appears to be too short'); process.exit(1); } - + return { PAYSTACK_TEST_SECRET_KEY: apiKey, NODE_ENV: (process.env.NODE_ENV as 'development' | 'production' | 'test') || 'development', diff --git a/src/data/paystack-skill.md b/src/data/paystack-skill.md index 8b44246..de91334 100644 --- a/src/data/paystack-skill.md +++ b/src/data/paystack-skill.md @@ -15,28 +15,32 @@ This index covers payments, transfers, terminal, guides, libraries, API referenc Code snippets for Paystack API endpoints and integration guides are maintained in the PaystackOSS/doc-code-snippets repository. Snippets are available in JavaScript (Node.js), Shell/cURL, and PHP. ### Browsing Snippets + - API Reference snippets: https://github.com/PaystackOSS/doc-code-snippets/tree/main/src/api - Documentation snippets: https://github.com/PaystackOSS/doc-code-snippets/tree/main/src/doc ### Fetching a Specific Snippet + Once you know the exact path from browsing, fetch the raw content at: https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/src/api/{topic}/{action}/index.{js,sh} For example: + - https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/src/api/transactions/initialize/index.js - https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/src/api/transactions/initialize/index.sh ### Snippet Notes + - Snippets use placeholder values like "SECRET_KEY" or "YOUR_SECRET_KEY" — replace with actual test keys - Not every endpoint has a snippet; if unavailable, construct the request from the operation details provided by the "get_paystack_operation" tool -- The supported webhook events are in https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/dist/doc/payments/webhooks/events.js +- The supported webhook events are in https://raw.githubusercontent.com/PaystackOSS/doc-code-snippets/main/dist/doc/payments/webhooks/events.js ## Payment Channels by Country -| Country | Currencies | Payment Channels | -|---------|----------|-----------------| -| Nigeria | NGN, USD | Cards (Visa, Mastercard, Verve, Amex), Bank Transfer, USSD, QR Code, Apple Pay | -| Ghana | GHS | Cards (Visa, Mastercard, Verve), Mobile Money (MTN, AirtelTigo, Telecel), Bank Transfer, QR Code | -| South Africa | ZAR | Cards (Visa, Mastercard, Verve, Amex), Apple Pay, Scan to Pay, Capitec Pay, Ozow | -| Kenya | KES, USD | Cards (Visa, Mastercard, Verve), Mobile Money (M-PESA, Airtel Money), Bank Transfers (Pesalink) -| Côte d'Ivoire | XOF | Cards (Visa, Mastercard, Verve), Mobile Money (MTN MoMo, Wave, Orange Money), Apple Pay | \ No newline at end of file +| Country | Currencies | Payment Channels | +| ------------- | ---------- | ------------------------------------------------------------------------------------------------ | +| Nigeria | NGN, USD | Cards (Visa, Mastercard, Verve, Amex), Bank Transfer, USSD, QR Code, Apple Pay | +| Ghana | GHS | Cards (Visa, Mastercard, Verve), Mobile Money (MTN, AirtelTigo, Telecel), Bank Transfer, QR Code | +| South Africa | ZAR | Cards (Visa, Mastercard, Verve, Amex), Apple Pay, Scan to Pay, Capitec Pay, Ozow | +| Kenya | KES, USD | Cards (Visa, Mastercard, Verve), Mobile Money (M-PESA, Airtel Money), Bank Transfers (Pesalink) | +| Côte d'Ivoire | XOF | Cards (Visa, Mastercard, Verve), Mobile Money (MTN MoMo, Wave, Orange Money), Apple Pay | diff --git a/src/data/paystack.openapi.yaml b/src/data/paystack.openapi.yaml index 9663923..a798e5c 100644 --- a/src/data/paystack.openapi.yaml +++ b/src/data/paystack.openapi.yaml @@ -19140,4 +19140,4 @@ components: content: application/json: schema: - $ref: '#/components/schemas/MiscellaneousListStatesResponse' \ No newline at end of file + $ref: '#/components/schemas/MiscellaneousListStatesResponse' diff --git a/src/index.ts b/src/index.ts index 0256b0b..4d6d9a0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,12 +2,12 @@ // Simple CLI argument parsing function parseApiKey(): string | undefined { const args = process.argv; - const apiKeyIndex = args.findIndex(arg => arg === '--api-key'); - + const apiKeyIndex = args.findIndex((arg) => arg === '--api-key'); + if (apiKeyIndex !== -1 && apiKeyIndex + 1 < args.length) { return args[apiKeyIndex + 1]; } - + return undefined; } @@ -39,24 +39,25 @@ async function main() { process.exit(0); } - const { startServer } = await import("./server"); + const { startServer } = await import('./server'); // Parse API key from CLI const cliApiKey = parseApiKey(); - - + // Check if we have an API key from CLI or environment if (!cliApiKey && !process.env.PAYSTACK_TEST_SECRET_KEY) { console.error('Error: Paystack API key required.'); - console.error('Provide via --api-key argument or PAYSTACK_TEST_SECRET_KEY environment variable.'); + console.error( + 'Provide via --api-key argument or PAYSTACK_TEST_SECRET_KEY environment variable.', + ); showHelp(); process.exit(1); } - + await startServer(cliApiKey); } main().catch((error) => { - console.error("Fatal error in main():", error); + console.error('Fatal error in main():', error); process.exit(1); -}); \ No newline at end of file +}); diff --git a/src/logger.ts b/src/logger.ts index a6aa82d..7cdc39a 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -35,7 +35,7 @@ function redactCardObject(card: any): any { if (Array.isArray(card)) { return card.map(redactCardObject); } - + if (typeof card === 'object' && card !== null) { const redactedCard: any = {}; for (const [key, value] of Object.entries(card)) { @@ -49,11 +49,10 @@ function redactCardObject(card: any): any { } return redactedCard; } - + return card; } - function redactSensitiveData(obj: any): any { if (obj === null || obj === undefined) { return obj; @@ -61,8 +60,9 @@ function redactSensitiveData(obj: any): any { if (typeof obj === 'string') { // Redact bearer tokens and API keys in strings - return obj.replace(/Bearer\s+\w+/gi, 'Bearer [REDACTED]') - .replace(/sk_test_\w+/g, '[REDACTED_SECRET_KEY]'); + return obj + .replace(/Bearer\s+\w+/gi, 'Bearer [REDACTED]') + .replace(/sk_test_\w+/g, '[REDACTED_SECRET_KEY]'); } if (Array.isArray(obj)) { @@ -75,9 +75,9 @@ function redactSensitiveData(obj: any): any { // Special handling for card objects - only redact cvv and number if (key.toLowerCase() === 'card' && typeof value === 'object' && value !== null) { redacted[key] = redactCardObject(value); - } + } // Check if key matches sensitive patterns - else if (SENSITIVE_PATTERNS.some(pattern => pattern.test(key))) { + else if (SENSITIVE_PATTERNS.some((pattern) => pattern.test(key))) { redacted[key] = '[REDACTED]'; } else { redacted[key] = redactSensitiveData(value); diff --git a/src/openapi-parser.ts b/src/openapi-parser.ts index 7edb595..a45ed53 100644 --- a/src/openapi-parser.ts +++ b/src/openapi-parser.ts @@ -16,56 +16,46 @@ export class OpenAPIParser { async parse() { try { - const api = (await SwaggerParser.default.parse( - this.specURL - )) as OpenAPIV3.Document; + const api = (await SwaggerParser.default.parse(this.specURL)) as OpenAPIV3.Document; this.buildOperationsMap(api); } catch (error) { console.error('Error parsing OpenAPI spec:', error); } } - private parseModel( - name: string, - models: OpenAPIV3.ComponentsObject, - ): Oas.Body { - const { schemas } = models! + private parseModel(name: string, models: OpenAPIV3.ComponentsObject): Oas.Body { + const { schemas } = models!; const model = schemas![name] as OpenAPIV3.SchemaObject; const body = {} as Oas.Body; - body.properties = [] + body.properties = []; if (model.properties === undefined) { - body.properties.push({ ...model as Oas.BodyParam }) - return body + body.properties.push({ ...(model as Oas.BodyParam) }); + return body; } const { properties, required, example } = model; Object.entries(properties).forEach(([key, property]) => { - let bodyParam = {} as Oas.BodyParam + let bodyParam = {} as Oas.BodyParam; - if ("$ref" in property) { - const name = property.$ref.split("/").pop()!; + if ('$ref' in property) { + const name = property.$ref.split('/').pop()!; // TODO: Consider parsing inner examples if available // Maybe just merge to the top example - const { properties: innerParams } = this.parseModel(name, models) + const { properties: innerParams } = this.parseModel(name, models); bodyParam = { - ...innerParams[0] - } - bodyParam.name = key + ...innerParams[0], + }; + bodyParam.name = key; } else { - const tempProperty = property as OpenAPIV3.SchemaObject + const tempProperty = property as OpenAPIV3.SchemaObject; bodyParam = { name: key, type: tempProperty.type!, - description: tempProperty.description ?? "", - required: - required === undefined - ? false - : required.includes(key) - ? true - : false, + description: tempProperty.description ?? '', + required: required === undefined ? false : required.includes(key) ? true : false, }; } @@ -73,7 +63,7 @@ export class OpenAPIParser { }); if (example !== undefined) { - body.examples = example as Map + body.examples = example as Map; } return body; } @@ -82,81 +72,78 @@ export class OpenAPIParser { schema: Oas.PropertySchema, models: OpenAPIV3.ComponentsObject, ): Oas.Body { - - if ("$ref" in schema) { - const name = schema.$ref.split("/").pop()!; + if ('$ref' in schema) { + const name = schema.$ref.split('/').pop()!; const body = this.parseModel(name, models); return body; } - const body = {} as Oas.Body - body.properties = [] + const body = {} as Oas.Body; + body.properties = []; - if ("type" in schema && schema.type === "array") { + if ('type' in schema && schema.type === 'array') { const schemaPath = schema.items as OpenAPIV3.ReferenceObject; - const name = schemaPath.$ref.split("/").pop() + const name = schemaPath.$ref.split('/').pop(); - const { properties, examples } = this.parseModel(name!, models) + const { properties, examples } = this.parseModel(name!, models); const parent = { - name: "", + name: '', type: 'array', required: true, - description: schema.description ?? "", - children: properties - } as Oas.BodyParam + description: schema.description ?? '', + children: properties, + } as Oas.BodyParam; - body.properties.push(parent) - body.examples = examples + body.properties.push(parent); + body.examples = examples; return body; } - if ("type" in schema && schema.type === "object") { + if ('type' in schema && schema.type === 'object') { const [key, path] = Object.entries(schema.properties!)[0]; - const schemaPath = path as OpenAPIV3.ReferenceObject - const name = schemaPath.$ref.split("/").pop()!; - const { properties, examples } = this.parseModel(name, models) + const schemaPath = path as OpenAPIV3.ReferenceObject; + const name = schemaPath.$ref.split('/').pop()!; + const { properties, examples } = this.parseModel(name, models); const parent = { name: key, type: 'object', required: schema.required ?? false, - description: schema.description ?? "", - children: properties - } as Oas.BodyParam + description: schema.description ?? '', + children: properties, + } as Oas.BodyParam; - body.properties.push(parent) - body.examples = examples + body.properties.push(parent); + body.examples = examples; return body; } - if ("allOf" in schema) { + if ('allOf' in schema) { // An object is being used here because the OAS examples are actually of // the object type even though they're typed as Map // Also, map isn't being parsed properly when writing to file - let mergedExamples = {} + let mergedExamples = {}; schema.allOf?.forEach((schema) => { - const { properties, examples } = this.parseRequestBody(schema, models) - body.properties.push(...properties) + const { properties, examples } = this.parseRequestBody(schema, models); + body.properties.push(...properties); if (examples !== undefined) { - mergedExamples = { ...mergedExamples, ...examples } + mergedExamples = { ...mergedExamples, ...examples }; } }); - body.examples = mergedExamples as Map + body.examples = mergedExamples as Map; - return body + return body; } return {} as Oas.Body; } - private parseParameters( - parameters: OpenAPIV3.ParameterObject[] - ): Oas.RequestParameter { - let result: Oas.RequestParameter = { + private parseParameters(parameters: OpenAPIV3.ParameterObject[]): Oas.RequestParameter { + const result: Oas.RequestParameter = { // header: [], pathParameter: [], queryParameter: [], @@ -165,17 +152,17 @@ export class OpenAPIParser { parameters.forEach((parameter) => { const endpointParam: Oas.EndpointParam = { name: parameter.name, - description: parameter.description ?? "", + description: parameter.description ?? '', required: parameter.required ?? false, schema: parameter.schema as OpenAPIV3.SchemaObject, example: parameter.example, }; - if (parameter.in === "query") { + if (parameter.in === 'query') { result.queryParameter.push(endpointParam); } - if (parameter.in === "path") { + if (parameter.in === 'path') { result.pathParameter.push(endpointParam); } }); @@ -214,31 +201,31 @@ export class OpenAPIParser { if (requestBody !== undefined) { const { content } = requestBody as OpenAPIV3.RequestBodyObject; - const mediaType = content["application/json"]; + const mediaType = content['application/json']; const body = mediaType.schema === undefined - ? {} as Oas.Body + ? ({} as Oas.Body) : this.parseRequestBody(mediaType.schema, components!); - partialOperation.requestBody = body + partialOperation.requestBody = body; } if (parameters && parameters !== null && parameters.length > 0) { - const { pathParameter, queryParameter } = - this.parseParameters(parameters as OpenAPIV3.ParameterObject[]); + const { pathParameter, queryParameter } = this.parseParameters( + parameters as OpenAPIV3.ParameterObject[], + ); // partialOperation.header = header; partialOperation.pathParameter = pathParameter; partialOperation.queryParameter = queryParameter; } - this.operations[operationId!] = partialOperation + this.operations[operationId!] = partialOperation; }); }); } - getOperationById(operationId: string): Partial | undefined { return this.operations[operationId]; } @@ -246,4 +233,4 @@ export class OpenAPIParser { getOperations(): Record> { return this.operations; } -} \ No newline at end of file +} diff --git a/src/paystack-client.ts b/src/paystack-client.ts index b539751..89dd8ed 100644 --- a/src/paystack-client.ts +++ b/src/paystack-client.ts @@ -1,5 +1,5 @@ -import { PaystackResponse, PaystackError } from "./types"; -import { createPaystackConfig } from "./config"; +import { PaystackResponse, PaystackError } from './types'; +import { createPaystackConfig } from './config'; const PAYSTACK_BASE_URL = 'https://api.paystack.co'; const USER_AGENT = process.env.USER_AGENT || 'Paystack-MCP-Server/0.0.1'; @@ -14,10 +14,10 @@ export class PaystackClient { secretKey: string, baseUrl: string = PAYSTACK_BASE_URL, userAgent: string = USER_AGENT, - timeout: number = 30000 + timeout: number = 30000, ) { if (!secretKey) { - throw new Error("Paystack secret key is required"); + throw new Error('Paystack secret key is required'); } this.secretKey = secretKey; @@ -33,22 +33,17 @@ export class PaystackClient { * @param data - Request body for POST/PUT/PATCH or query params for GET */ - async makeRequest( - method: string, - endpoint: string, - data?: any - ): Promise> { - - let url = `${this.baseUrl}${endpoint}`; + async makeRequest(method: string, endpoint: string, data?: any): Promise> { + const url = `${this.baseUrl}${endpoint}`; const headers: Record = { - 'Authorization': `Bearer ${this.secretKey}`, + Authorization: `Bearer ${this.secretKey}`, 'User-Agent': this.userAgent, - 'Accept': 'application/json', + Accept: 'application/json', }; const options: RequestInit = { - method: method.toUpperCase() + method: method.toUpperCase(), }; // Add Content-Type and body for requests with data @@ -67,11 +62,10 @@ export class PaystackClient { try { responseData = JSON.parse(responseText); - } catch (parseError) { + } catch { // Handle non-JSON responses gracefully (e.g., HTML error pages from API gateways) - const responseSnippet = responseText.length > 200 - ? responseText.substring(0, 200) + '...' - : responseText; + const responseSnippet = + responseText.length > 200 ? responseText.substring(0, 200) + '...' : responseText; const errorMessage = `Received non-JSON response from server (HTTP ${response.status}): ${responseSnippet}`; const nonJsonError = new Error(errorMessage); (nonJsonError as any).statusCode = response.status; @@ -80,7 +74,6 @@ export class PaystackClient { } return responseData as PaystackResponse; } catch (error) { - if (error !== null && (error as any).name === 'NetworkError') { const timeoutError = new Error(`Request timeout after ${this.timeout} ms`); (timeoutError as any).statusCode = 408; @@ -88,8 +81,7 @@ export class PaystackClient { } throw error; } - -} + } } /** diff --git a/src/resources/index.ts b/src/resources/index.ts index 74dc2d6..a68586d 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -1,12 +1,12 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { OpenAPIParser } from "../openapi-parser"; -import { registerOperationListResource } from "./paystack-operation-list"; -import { registerSkillResource } from "./paystack-skill"; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { OpenAPIParser } from '../openapi-parser'; +import { registerOperationListResource } from './paystack-operation-list'; +import { registerSkillResource } from './paystack-skill'; export function registerAllResources( server: McpServer, openapi: OpenAPIParser, - skillContent: string + skillContent: string, ) { registerOperationListResource(server, openapi); registerSkillResource(server, skillContent); diff --git a/src/resources/paystack-operation-list.ts b/src/resources/paystack-operation-list.ts index 3c45776..c0c903f 100644 --- a/src/resources/paystack-operation-list.ts +++ b/src/resources/paystack-operation-list.ts @@ -1,17 +1,14 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { OpenAPIParser } from "../openapi-parser"; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { OpenAPIParser } from '../openapi-parser'; -export function registerOperationListResource( - server: McpServer, - openapi: OpenAPIParser -) { +export function registerOperationListResource(server: McpServer, openapi: OpenAPIParser) { server.registerResource( - "paystack_operation_list", - "paystack://operations/list", + 'paystack_operation_list', + 'paystack://operations/list', { - description: "Retrieve all Paystack API details", - title: "Paystack API details", - mimeType: "application/json", + description: 'Retrieve all Paystack API details', + title: 'Paystack API details', + mimeType: 'application/json', }, async (uri) => { const operations = openapi.getOperations(); @@ -21,19 +18,21 @@ export function registerOperationListResource( contents: [ { uri: uri.href, - text: JSON.stringify({"message": "Unable to retrive all operations"}), - mimeType: "application/json", + text: JSON.stringify({ message: 'Unable to retrive all operations' }), + mimeType: 'application/json', }, - ] - } + ], + }; } - return { - contents: [{ - uri: uri.href, - text: JSON.stringify(operations, null, 2), - mimeType: "application/json" - }] + return { + contents: [ + { + uri: uri.href, + text: JSON.stringify(operations, null, 2), + mimeType: 'application/json', + }, + ], }; - } + }, ); } diff --git a/src/resources/paystack-skill.ts b/src/resources/paystack-skill.ts index 5f292de..3396ac9 100644 --- a/src/resources/paystack-skill.ts +++ b/src/resources/paystack-skill.ts @@ -1,14 +1,14 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; export function registerSkillResource(server: McpServer, skillContent: string) { server.registerResource( - "paystack_skill", - "paystack://skill", + 'paystack_skill', + 'paystack://skill', { description: - "Paystack developer knowledge: docs index pointer, code snippet URL patterns, and payment channel reference", - title: "Paystack Developer Knowledge", - mimeType: "text/markdown", + 'Paystack developer knowledge: docs index pointer, code snippet URL patterns, and payment channel reference', + title: 'Paystack Developer Knowledge', + mimeType: 'text/markdown', }, async (uri) => { return { @@ -16,10 +16,10 @@ export function registerSkillResource(server: McpServer, skillContent: string) { { uri: uri.href, text: skillContent, - mimeType: "text/markdown", + mimeType: 'text/markdown', }, ], }; - } + }, ); } diff --git a/src/server.ts b/src/server.ts index e3bed24..83b79b8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,10 +1,10 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import path from "path"; -import { OpenAPIParser } from "./openapi-parser"; -import { registerAllTools } from "./tools"; -import { registerAllResources } from "./resources"; -import { loadSkillContent } from "./skill-loader"; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; +import path from 'path'; +import { OpenAPIParser } from './openapi-parser'; +import { registerAllTools } from './tools'; +import { registerAllResources } from './resources'; +import { loadSkillContent } from './skill-loader'; const SERVER_INSTRUCTIONS = `You are connected to the Paystack MCP Server, which provides access to the full Paystack API. @@ -30,19 +30,22 @@ Always call "get_paystack_operation" to get endpoint details before calling "mak `; async function createServer(cliApiKey?: string) { - const server = new McpServer({ - name: "paystack", - version: "0.0.1", - }, { - instructions: SERVER_INSTRUCTIONS, - }); - - const oasPath = path.join(__dirname, "./", "data/paystack.openapi.yaml"); + const server = new McpServer( + { + name: 'paystack', + version: '0.0.1', + }, + { + instructions: SERVER_INSTRUCTIONS, + }, + ); + + const oasPath = path.join(__dirname, './', 'data/paystack.openapi.yaml'); const openapi = new OpenAPIParser(oasPath); await openapi.parse(); - const bundledSkillPath = path.join(__dirname, "data", "paystack-skill.md"); + const bundledSkillPath = path.join(__dirname, 'data', 'paystack-skill.md'); const skillContent = await loadSkillContent(bundledSkillPath); registerAllTools(server, openapi, cliApiKey); @@ -55,6 +58,6 @@ export async function startServer(cliApiKey?: string) { const server = await createServer(cliApiKey); const transport = new StdioServerTransport(); await server.connect(transport); - console.error("Paystack MCP Server running on stdio..."); + console.error('Paystack MCP Server running on stdio...'); return server; -} \ No newline at end of file +} diff --git a/src/skill-loader.ts b/src/skill-loader.ts index 7b3d53b..459151b 100644 --- a/src/skill-loader.ts +++ b/src/skill-loader.ts @@ -1,12 +1,12 @@ -import * as fs from "node:fs/promises"; -import * as path from "node:path"; -import { homedir } from "node:os"; +import * as fs from 'node:fs/promises'; +import * as path from 'node:path'; +import { homedir } from 'node:os'; const DEFAULT_SKILL_URL = - "https://raw.githubusercontent.com/PaystackOSS/paystack-mcp-server/main/src/data/paystack-skill.md"; + 'https://raw.githubusercontent.com/PaystackOSS/paystack-mcp-server/main/src/data/paystack-skill.md'; -const CACHE_DIR = path.join(homedir(), ".paystack-mcp"); -const CACHE_PATH = path.join(CACHE_DIR, "skill-cache.md"); +const CACHE_DIR = path.join(homedir(), '.paystack-mcp'); +const CACHE_PATH = path.join(CACHE_DIR, 'skill-cache.md'); const MAX_AGE_MS = 48 * 60 * 60 * 1000; // 48 hours export async function loadSkillContent(bundledPath: string): Promise { @@ -16,7 +16,7 @@ export async function loadSkillContent(bundledPath: string): Promise { // Check disk cache freshness const stat = await fs.stat(CACHE_PATH); if (Date.now() - stat.mtimeMs < MAX_AGE_MS) { - return await fs.readFile(CACHE_PATH, "utf-8"); + return await fs.readFile(CACHE_PATH, 'utf-8'); } } catch { // Cache missing or unreadable — continue to fetch @@ -26,10 +26,10 @@ export async function loadSkillContent(bundledPath: string): Promise { const res = await fetch(url, { signal: AbortSignal.timeout(3000) }); if (res.ok) { const text = await res.text(); - if (text.includes("name: paystack")) { + if (text.includes('name: paystack')) { try { await fs.mkdir(CACHE_DIR, { recursive: true }); - await fs.writeFile(CACHE_PATH, text, "utf-8"); + await fs.writeFile(CACHE_PATH, text, 'utf-8'); } catch { // Cache write failed — non-fatal } @@ -42,8 +42,8 @@ export async function loadSkillContent(bundledPath: string): Promise { // Fallback: stale cache → bundled file try { - return await fs.readFile(CACHE_PATH, "utf-8"); + return await fs.readFile(CACHE_PATH, 'utf-8'); } catch { - return await fs.readFile(bundledPath, "utf-8"); + return await fs.readFile(bundledPath, 'utf-8'); } } diff --git a/src/tools/get-paystack-operation.ts b/src/tools/get-paystack-operation.ts index 148ac88..264dc8c 100644 --- a/src/tools/get-paystack-operation.ts +++ b/src/tools/get-paystack-operation.ts @@ -1,27 +1,24 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import * as z from "zod"; -import { OpenAPIParser } from "../openapi-parser"; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import * as z from 'zod'; +import { OpenAPIParser } from '../openapi-parser'; -export function registerGetPaystackOperationTool( - server: McpServer, - openapi: OpenAPIParser -) { +export function registerGetPaystackOperationTool(server: McpServer, openapi: OpenAPIParser) { const operations = openapi.getOperations(); server.registerTool( - "get_paystack_operation", + 'get_paystack_operation', { description: `Get Paystack API operation details by operation ID. Available operations are: - ${Object.keys(operations).map(operation => operation).join(", ") }`, + ${Object.keys(operations) + .map((operation) => operation) + .join(', ')}`, annotations: { - title: "Get endpoint details by operation ID", + title: 'Get endpoint details by operation ID', }, inputSchema: { - operation_id: z - .string() - .describe("The operation ID of the Paystack API endpoint"), - } + operation_id: z.string().describe('The operation ID of the Paystack API endpoint'), + }, }, async ({ operation_id }) => { try { @@ -31,35 +28,35 @@ export function registerGetPaystackOperationTool( return { content: [ { - type: "text", + type: 'text', text: `Operation with ID ${operation_id} not found.`, }, ], - isError: true - } + isError: true, + }; } return { content: [ { - type: "text", + type: 'text', text: JSON.stringify(operation, null, 2), - mimeType: "application/json", + mimeType: 'application/json', }, - ] - } + ], + }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { - type: "text", + type: 'text', text: `Error retrieving operation: ${errorMessage}`, }, ], - isError: true - } + isError: true, + }; } - } + }, ); } diff --git a/src/tools/index.ts b/src/tools/index.ts index e63eb49..18658de 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,13 +1,9 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { OpenAPIParser } from "../openapi-parser"; -import { registerGetPaystackOperationTool } from "./get-paystack-operation"; -import { registerMakePaystackRequestTool } from "./make-paystack-request"; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { OpenAPIParser } from '../openapi-parser'; +import { registerGetPaystackOperationTool } from './get-paystack-operation'; +import { registerMakePaystackRequestTool } from './make-paystack-request'; -export function registerAllTools( - server: McpServer, - openapi: OpenAPIParser, - cliApiKey?: string -) { +export function registerAllTools(server: McpServer, openapi: OpenAPIParser, cliApiKey?: string) { registerGetPaystackOperationTool(server, openapi); registerMakePaystackRequestTool(server, cliApiKey); } diff --git a/src/tools/make-paystack-request.ts b/src/tools/make-paystack-request.ts index 6adce0d..409c999 100644 --- a/src/tools/make-paystack-request.ts +++ b/src/tools/make-paystack-request.ts @@ -1,52 +1,52 @@ -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import * as z from "zod"; -import { PaystackClient } from "../paystack-client"; -import { createPaystackConfig } from "../config"; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import * as z from 'zod'; +import { PaystackClient } from '../paystack-client'; +import { createPaystackConfig } from '../config'; export function registerMakePaystackRequestTool(server: McpServer, cliApiKey?: string) { // Create PaystackClient with CLI API key or fallback to environment const config = createPaystackConfig(cliApiKey); const paystackClient = new PaystackClient(config.secretKey); - + server.registerTool( - "make_paystack_request", + 'make_paystack_request', { description: `Make a Paystack API request using the details of the operation. Be sure to get all operation details including method, path path parameters, query parameters, and request body before making a call.`, annotations: { - title: "Make a Paystack API request", + title: 'Make a Paystack API request', }, inputSchema: { request: z.object({ - method: z.string().describe("HTTP method of the API request"), - path: z.string().describe("Path of the API request"), - data: z.looseObject({}).optional().describe("Request data"), - }) - } + method: z.string().describe('HTTP method of the API request'), + path: z.string().describe('Path of the API request'), + data: z.looseObject({}).optional().describe('Request data'), + }), + }, }, async ({ request }) => { try { const response = await paystackClient.makeRequest( request.method, request.path, - request.data - ) + request.data, + ); return { content: [ { - type: "text", + type: 'text', text: JSON.stringify(response, null, 2), - mimeType: "application/json", + mimeType: 'application/json', }, - ] - } - } catch(error) { + ], + }; + } catch (error) { // Follow MCP best practices: return isError flag instead of throwing const errorMessage = error instanceof Error ? error.message : String(error); const statusCode = (error as any).statusCode; - + let detailedMessage = `Unable to make request: ${errorMessage}`; if (statusCode) { detailedMessage = `Unable to make request (HTTP ${statusCode}): ${errorMessage}`; @@ -55,13 +55,13 @@ export function registerMakePaystackRequestTool(server: McpServer, cliApiKey?: s return { content: [ { - type: "text", + type: 'text', text: detailedMessage, }, ], - isError: true - } + isError: true, + }; } - } + }, ); } diff --git a/src/types/client.ts b/src/types/client.ts index 41e5510..4a3e50e 100644 --- a/src/types/client.ts +++ b/src/types/client.ts @@ -17,7 +17,7 @@ export interface PaystackError { message: string; meta?: { nextStep?: string; - }, + }; type?: string; code?: string; -} \ No newline at end of file +} diff --git a/src/types/index.ts b/src/types/index.ts index f9d3852..3c47df3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,2 +1,2 @@ -export * as Oas from "./openapi-components"; -export * from "./client"; \ No newline at end of file +export * as Oas from './openapi-components'; +export * from './client'; diff --git a/src/types/openapi-components.ts b/src/types/openapi-components.ts index fa268c5..1472418 100644 --- a/src/types/openapi-components.ts +++ b/src/types/openapi-components.ts @@ -1,5 +1,4 @@ -import { OpenAPIV3 } from "openapi-types"; - +import { OpenAPIV3 } from 'openapi-types'; export interface Operation { // tag: string; @@ -8,56 +7,56 @@ export interface Operation { method: OpenAPIV3.HttpMethods; description: string; // operationId: string; - queryParameter: EndpointParam[] - pathParameter: EndpointParam[] + queryParameter: EndpointParam[]; + pathParameter: EndpointParam[]; requestBody: Body; // requestBody: OpenAPIV3.ReferenceObject | OpenAPIV3.RequestBodyObject; // responses: OpenAPIV3.ResponsesObject; } export type EndpointParam = { - name: string - description: string - required: boolean - schema: OpenAPIV3.SchemaObject - example: any -} + name: string; + description: string; + required: boolean; + schema: OpenAPIV3.SchemaObject; + example: any; +}; export type BodyParam = { - name: string - type: string - description: string - items?: OpenAPIV3.SchemaObject - required?: boolean - example?: any - children?: BodyParam[] -} + name: string; + type: string; + description: string; + items?: OpenAPIV3.SchemaObject; + required?: boolean; + example?: any; + children?: BodyParam[]; +}; export type Body = { - properties: BodyParam[] - examples: Map -} + properties: BodyParam[]; + examples: Map; +}; export type RequestParameter = { // header: EndpointParam[] - queryParameter: EndpointParam[] - pathParameter: EndpointParam[] -} + queryParameter: EndpointParam[]; + pathParameter: EndpointParam[]; +}; export type BaseEndpoint = { - id: string - name: string - description: string - path: string - method: OpenAPIV3.HttpMethods - body: Body -} + id: string; + name: string; + description: string; + path: string; + method: OpenAPIV3.HttpMethods; + body: Body; +}; -export type Endpoint = BaseEndpoint & RequestParameter +export type Endpoint = BaseEndpoint & RequestParameter; export type PathsOperations = Record>; export type PropertySchema = OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; // export type OperationDetails = { // path: string; // method: HttpMethod; -// } \ No newline at end of file +// } diff --git a/test/make-paystack-request-tool.spec.ts b/test/make-paystack-request-tool.spec.ts index 536fdb7..1992330 100644 --- a/test/make-paystack-request-tool.spec.ts +++ b/test/make-paystack-request-tool.spec.ts @@ -1,9 +1,9 @@ -import assert from "node:assert"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerMakePaystackRequestTool } from "../src/tools/make-paystack-request.js"; +import assert from 'node:assert'; +import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; +import { registerMakePaystackRequestTool } from '../src/tools/make-paystack-request.js'; -describe("MakePaystackRequestTool", () => { - describe("Error handling with isError flag", () => { +describe('MakePaystackRequestTool', () => { + describe('Error handling with isError flag', () => { let server: McpServer; let toolHandler: any; @@ -11,58 +11,58 @@ describe("MakePaystackRequestTool", () => { // Create a mock MCP server server = { registerTool: (name: string, config: any, handler: any) => { - if (name === "make_paystack_request") { + if (name === 'make_paystack_request') { toolHandler = handler; } - } + }, } as any; - // Pass a test API key to avoid environment variable requirement - registerMakePaystackRequestTool(server, "sk_test_1234567890abcdef1234567890abcdef12345678"); + // Pass a test API key to avoid environment variable requirement + registerMakePaystackRequestTool(server, 'sk_test_1234567890abcdef1234567890abcdef12345678'); }); - it("should return isError: true for non-JSON responses", async () => { + it('should return isError: true for non-JSON responses', async () => { // Mock fetch to return HTML error page const originalFetch = global.fetch; global.fetch = async () => { return { status: 502, - text: async () => "

502 Bad Gateway

", + text: async () => '

502 Bad Gateway

', } as Response; }; try { const result = await toolHandler({ request: { - method: "GET", - path: "/test-endpoint", - } + method: 'GET', + path: '/test-endpoint', + }, }); // Verify isError flag is set assert.strictEqual(result.isError, true); - + // Verify error message content assert.ok(result.content); assert.strictEqual(result.content.length, 1); - assert.strictEqual(result.content[0].type, "text"); - assert.ok(result.content[0].text.includes("Unable to make request")); - assert.ok(result.content[0].text.includes("HTTP 502")); - assert.ok(result.content[0].text.includes("non-JSON response")); + assert.strictEqual(result.content[0].type, 'text'); + assert.ok(result.content[0].text.includes('Unable to make request')); + assert.ok(result.content[0].text.includes('HTTP 502')); + assert.ok(result.content[0].text.includes('non-JSON response')); } finally { global.fetch = originalFetch; } }); - it("should omit isError for successful responses", async () => { + it('should omit isError for successful responses', async () => { // Mock fetch to return valid JSON const originalFetch = global.fetch; const validJsonResponse = { status: true, - message: "Success", - data: { id: 123 } + message: 'Success', + data: { id: 123 }, }; - + global.fetch = async () => { return { status: 200, @@ -73,80 +73,80 @@ describe("MakePaystackRequestTool", () => { try { const result = await toolHandler({ request: { - method: "GET", - path: "/test-endpoint", - } + method: 'GET', + path: '/test-endpoint', + }, }); // Verify isError is not set (or false) for successful responses assert.ok(!result.isError); - + // Verify success content assert.ok(result.content); assert.strictEqual(result.content.length, 1); - assert.strictEqual(result.content[0].type, "text"); - assert.strictEqual(result.content[0].mimeType, "application/json"); - + assert.strictEqual(result.content[0].type, 'text'); + assert.strictEqual(result.content[0].mimeType, 'application/json'); + // Parse and verify the response data const parsedResponse = JSON.parse(result.content[0].text); assert.strictEqual(parsedResponse.status, true); - assert.strictEqual(parsedResponse.message, "Success"); + assert.strictEqual(parsedResponse.message, 'Success'); } finally { global.fetch = originalFetch; } }); - it("should include HTTP status code in error message", async () => { + it('should include HTTP status code in error message', async () => { // Mock fetch to return a 504 Gateway Timeout const originalFetch = global.fetch; global.fetch = async () => { return { status: 504, - text: async () => "Gateway Timeout", + text: async () => 'Gateway Timeout', } as Response; }; try { const result = await toolHandler({ request: { - method: "POST", - path: "/transaction/initialize", - data: { amount: 1000 } - } + method: 'POST', + path: '/transaction/initialize', + data: { amount: 1000 }, + }, }); // Verify error response structure assert.strictEqual(result.isError, true); - assert.ok(result.content[0].text.includes("HTTP 504")); + assert.ok(result.content[0].text.includes('HTTP 504')); } finally { global.fetch = originalFetch; } }); - it("should handle network errors with isError flag", async () => { + it('should handle network errors with isError flag', async () => { // Mock fetch to simulate network error const originalFetch = global.fetch; global.fetch = async () => { - throw new Error("Network connection failed"); + throw new Error('Network connection failed'); }; try { const result = await toolHandler({ request: { - method: "GET", - path: "/customer/list", - } + method: 'GET', + path: '/customer/list', + }, }); // Verify error is properly handled assert.strictEqual(result.isError, true); - assert.ok(result.content[0].text.includes("Unable to make request")); + assert.ok(result.content[0].text.includes('Unable to make request')); } finally { global.fetch = originalFetch; } }); }); - describe("Missing API Key Validation", () => { + describe('Missing API Key Validation', () => { let server: McpServer; let originalExit: typeof process.exit; let exitCode: number | undefined; @@ -170,7 +170,7 @@ describe("MakePaystackRequestTool", () => { }; server = { - registerTool: (name: string, config: any, handler: any) => { } + registerTool: (_name: string, _config: any, _handler: any) => {}, } as any; }); @@ -180,17 +180,19 @@ describe("MakePaystackRequestTool", () => { delete process.env.PAYSTACK_TEST_SECRET_KEY; }); - it("should fail when no API key provided via CLI or environment", () => { + it('should fail when no API key provided via CLI or environment', () => { // Ensure no environment variable is set delete process.env.PAYSTACK_TEST_SECRET_KEY; try { registerMakePaystackRequestTool(server); // No cliApiKey parameter - assert.fail("Expected registerMakePaystackRequestTool to throw an error"); + assert.fail('Expected registerMakePaystackRequestTool to throw an error'); } catch (error: any) { - assert.ok(error.message.includes("Process would exit with code 1")); + assert.ok(error.message.includes('Process would exit with code 1')); assert.strictEqual(exitCode, 1); - assert.ok(consoleErrors.some(msg => msg.includes("PAYSTACK_TEST_SECRET_KEY is required"))); + assert.ok( + consoleErrors.some((msg) => msg.includes('PAYSTACK_TEST_SECRET_KEY is required')), + ); } }); }); diff --git a/test/openapi-parser.spec.ts b/test/openapi-parser.spec.ts index 88d8493..7006e41 100644 --- a/test/openapi-parser.spec.ts +++ b/test/openapi-parser.spec.ts @@ -1,19 +1,19 @@ -import assert from "node:assert"; -import path from "node:path"; -import { OpenAPIParser} from "../src/openapi-parser.js"; +import assert from 'node:assert'; +import path from 'node:path'; +import { OpenAPIParser } from '../src/openapi-parser.js'; -describe("OpenAPI parsing", () => { - const oasPath = path.join(__dirname, "../", "src/data/paystack.openapi.yaml"); +describe('OpenAPI parsing', () => { + const oasPath = path.join(__dirname, '../', 'src/data/paystack.openapi.yaml'); const openapi = new OpenAPIParser(oasPath); before(async () => { await openapi.parse(); }); - describe("Get operation by ID", () => { - it("should return operation name given the operation ID", () => { - const op = openapi.getOperationById("transaction_partialDebit"); - assert.equal("Partial Debit", op?.name); + describe('Get operation by ID', () => { + it('should return operation name given the operation ID', () => { + const op = openapi.getOperationById('transaction_partialDebit'); + assert.equal('Partial Debit', op?.name); }); - }) -}); \ No newline at end of file + }); +}); diff --git a/test/paystack-client.spec.ts b/test/paystack-client.spec.ts index ce98c8d..6a11c82 100644 --- a/test/paystack-client.spec.ts +++ b/test/paystack-client.spec.ts @@ -1,48 +1,48 @@ -import assert from "node:assert"; -import { createPaystackClient } from "../src/paystack-client.js"; +import assert from 'node:assert'; +import { createPaystackClient } from '../src/paystack-client.js'; -describe("PaystackClient", () => { +describe('PaystackClient', () => { // Use a test API key for the test client - const paystackClient = createPaystackClient("sk_test_1234567890abcdef1234567890abcdef12345678"); - - describe("makeRequest - Non-JSON Response Handling", () => { - it("should throw a descriptive error for HTML error responses", async () => { + const paystackClient = createPaystackClient('sk_test_1234567890abcdef1234567890abcdef12345678'); + + describe('makeRequest - Non-JSON Response Handling', () => { + it('should throw a descriptive error for HTML error responses', async () => { // This test validates that non-JSON responses (like HTML error pages) // are handled gracefully with proper error messages including status code - + // Mock fetch to return an HTML 502 Bad Gateway response const originalFetch = global.fetch; global.fetch = async () => { return { status: 502, - text: async () => "

502 Bad Gateway

", + text: async () => '

502 Bad Gateway

', } as Response; }; try { - await paystackClient.makeRequest("GET", "/test-endpoint"); - assert.fail("Expected makeRequest to throw an error"); + await paystackClient.makeRequest('GET', '/test-endpoint'); + assert.fail('Expected makeRequest to throw an error'); } catch (error: any) { // Verify error message includes status code and response snippet - assert.ok(error.message.includes("Received non-JSON response from server")); - assert.ok(error.message.includes("HTTP 502")); - assert.ok(error.message.includes("")); - + assert.ok(error.message.includes('Received non-JSON response from server')); + assert.ok(error.message.includes('HTTP 502')); + assert.ok(error.message.includes('')); + // Verify statusCode is attached to error assert.strictEqual(error.statusCode, 502); - + // Verify full responseText is available for debugging assert.ok(error.responseText); - assert.ok(error.responseText.includes("502 Bad Gateway")); + assert.ok(error.responseText.includes('502 Bad Gateway')); } finally { global.fetch = originalFetch; } }); - it("should truncate long non-JSON responses to 200 characters", async () => { + it('should truncate long non-JSON responses to 200 characters', async () => { const originalFetch = global.fetch; - const longHtmlResponse = "" + "x".repeat(300) + ""; - + const longHtmlResponse = '' + 'x'.repeat(300) + ''; + global.fetch = async () => { return { status: 500, @@ -51,20 +51,20 @@ describe("PaystackClient", () => { }; try { - await paystackClient.makeRequest("GET", "/test-endpoint"); - assert.fail("Expected makeRequest to throw an error"); + await paystackClient.makeRequest('GET', '/test-endpoint'); + assert.fail('Expected makeRequest to throw an error'); } catch (error: any) { // Verify the error message contains truncated snippet (200 chars + '...') const snippetMatch = error.message.match(/: (.+)$/); assert.ok(snippetMatch); const snippet = snippetMatch[1]; - + // Should end with '...' for truncation assert.ok(snippet.endsWith('...')); - + // Should be 203 characters (200 + '...') assert.ok(snippet.length <= 203); - + // Full response should still be available assert.strictEqual(error.responseText, longHtmlResponse); } finally { @@ -72,10 +72,10 @@ describe("PaystackClient", () => { } }); - it("should not truncate short non-JSON responses", async () => { + it('should not truncate short non-JSON responses', async () => { const originalFetch = global.fetch; - const shortResponse = "Gateway Timeout"; - + const shortResponse = 'Gateway Timeout'; + global.fetch = async () => { return { status: 504, @@ -84,8 +84,8 @@ describe("PaystackClient", () => { }; try { - await paystackClient.makeRequest("GET", "/test-endpoint"); - assert.fail("Expected makeRequest to throw an error"); + await paystackClient.makeRequest('GET', '/test-endpoint'); + assert.fail('Expected makeRequest to throw an error'); } catch (error: any) { // Verify the error message contains full short response assert.ok(error.message.includes(shortResponse)); @@ -96,14 +96,14 @@ describe("PaystackClient", () => { } }); - it("should successfully parse valid JSON responses", async () => { + it('should successfully parse valid JSON responses', async () => { const originalFetch = global.fetch; const validJsonResponse = { status: true, - message: "Success", - data: { id: 123 } + message: 'Success', + data: { id: 123 }, }; - + global.fetch = async () => { return { status: 200, @@ -112,9 +112,9 @@ describe("PaystackClient", () => { }; try { - const response = await paystackClient.makeRequest("GET", "/test-endpoint"); + const response = await paystackClient.makeRequest('GET', '/test-endpoint'); assert.strictEqual(response.status, true); - assert.strictEqual(response.message, "Success"); + assert.strictEqual(response.message, 'Success'); assert.deepStrictEqual(response.data, { id: 123 }); } finally { global.fetch = originalFetch; diff --git a/test/paystack-skill-resource.spec.ts b/test/paystack-skill-resource.spec.ts index fc97316..08916a7 100644 --- a/test/paystack-skill-resource.spec.ts +++ b/test/paystack-skill-resource.spec.ts @@ -1,15 +1,14 @@ -import assert from "node:assert"; -import * as fs from "node:fs"; -import * as path from "node:path"; -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { registerSkillResource } from "../src/resources/paystack-skill.js"; +import assert from 'node:assert'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { registerSkillResource } from '../src/resources/paystack-skill.js'; const SKILL_CONTENT = fs.readFileSync( - path.join(__dirname, "..", "src", "data", "paystack-skill.md"), - "utf-8" + path.join(__dirname, '..', 'src', 'data', 'paystack-skill.md'), + 'utf-8', ); -describe("PaystackSkillResource", () => { +describe('PaystackSkillResource', () => { let resourceHandler: any; let registeredName: string; let registeredUri: string; @@ -22,107 +21,104 @@ describe("PaystackSkillResource", () => { registeredUri = uri; registeredMetadata = metadata; resourceHandler = handler; - } + }, } as any; registerSkillResource(server, SKILL_CONTENT); }); - describe("Registration", () => { - it("should register with the correct name", () => { - assert.strictEqual(registeredName, "paystack_skill"); + describe('Registration', () => { + it('should register with the correct name', () => { + assert.strictEqual(registeredName, 'paystack_skill'); }); - it("should register with the correct URI", () => { - assert.strictEqual(registeredUri, "paystack://skill"); + it('should register with the correct URI', () => { + assert.strictEqual(registeredUri, 'paystack://skill'); }); - it("should set mimeType to text/markdown", () => { - assert.strictEqual(registeredMetadata.mimeType, "text/markdown"); + it('should set mimeType to text/markdown', () => { + assert.strictEqual(registeredMetadata.mimeType, 'text/markdown'); }); - it("should have a description", () => { + it('should have a description', () => { assert.ok(registeredMetadata.description); assert.ok(registeredMetadata.description.length > 0); }); }); - describe("Content", () => { + describe('Content', () => { let content: string; before(async () => { - const mockUri = new URL("paystack://skill"); + const mockUri = new URL('paystack://skill'); const result = await resourceHandler(mockUri); content = result.contents[0].text; }); - it("should return text/markdown mimeType in response", async () => { - const mockUri = new URL("paystack://skill"); + it('should return text/markdown mimeType in response', async () => { + const mockUri = new URL('paystack://skill'); const result = await resourceHandler(mockUri); - assert.strictEqual(result.contents[0].mimeType, "text/markdown"); + assert.strictEqual(result.contents[0].mimeType, 'text/markdown'); }); - it("should include documentation index section", () => { - assert.ok(content.includes("## Documentation Index")); + it('should include documentation index section', () => { + assert.ok(content.includes('## Documentation Index')); }); - it("should include code snippets section", () => { - assert.ok(content.includes("## Code Snippets")); + it('should include code snippets section', () => { + assert.ok(content.includes('## Code Snippets')); }); - it("should include payment channels by country section", () => { - assert.ok(content.includes("## Payment Channels by Country")); + it('should include payment channels by country section', () => { + assert.ok(content.includes('## Payment Channels by Country')); }); - it("should include links to Paystack docs", () => { - assert.ok(content.includes("https://paystack.com/docs/llms.txt")); + it('should include links to Paystack docs', () => { + assert.ok(content.includes('https://paystack.com/docs/llms.txt')); }); - it("should include snippet repo URL pattern for JS", () => { - assert.ok(content.includes("PaystackOSS/doc-code-snippets")); - assert.ok(content.includes("index.js")); + it('should include snippet repo URL pattern for JS', () => { + assert.ok(content.includes('PaystackOSS/doc-code-snippets')); + assert.ok(content.includes('index.js')); }); - it("should include snippet repo URL pattern for Shell", () => { - assert.ok(content.includes("index.sh")); + it('should include snippet repo URL pattern for Shell', () => { + assert.ok(content.includes('index.sh')); }); - it("should include all supported countries", () => { - assert.ok(content.includes("Nigeria")); - assert.ok(content.includes("Ghana")); - assert.ok(content.includes("South Africa")); - assert.ok(content.includes("Kenya")); + it('should include all supported countries', () => { + assert.ok(content.includes('Nigeria')); + assert.ok(content.includes('Ghana')); + assert.ok(content.includes('South Africa')); + assert.ok(content.includes('Kenya')); assert.ok(content.includes("Côte d'Ivoire")); }); }); }); -describe("ServerInstructions", () => { - it("should pass instructions to the McpServer constructor", async () => { +describe('ServerInstructions', () => { + it('should pass instructions to the McpServer constructor', async () => { // Dynamically import server module to verify instructions are set // We check the source directly since McpServer options are private - const fs = await import("node:fs"); - const path = await import("node:path"); - const serverSource = fs.readFileSync( - path.join(__dirname, "../src/server.ts"), - "utf-8" - ); + const fs = await import('node:fs'); + const path = await import('node:path'); + const serverSource = fs.readFileSync(path.join(__dirname, '../src/server.ts'), 'utf-8'); assert.ok( - serverSource.includes("instructions:"), - "server.ts should pass instructions to McpServer" + serverSource.includes('instructions:'), + 'server.ts should pass instructions to McpServer', ); assert.ok( - serverSource.includes("smallest currency unit"), - "instructions should include currency unit rule" + serverSource.includes('smallest currency unit'), + 'instructions should include currency unit rule', ); assert.ok( - serverSource.includes("get_paystack_operation"), - "instructions should reference the operation tool" + serverSource.includes('get_paystack_operation'), + 'instructions should reference the operation tool', ); assert.ok( - serverSource.includes("do not invent"), - "instructions should include anti-hallucination directive" + serverSource.includes('do not invent'), + 'instructions should include anti-hallucination directive', ); }); }); diff --git a/tsconfig.json b/tsconfig.json index f0a352a..193a51d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,6 +18,6 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true }, - "include": ["src/**/*", "tests/**/*"], + "include": ["src/**/*"], "exclude": ["node_modules"] } \ No newline at end of file