From 7fd696382a776f36fcc1057c8024a4bd13cc7e27 Mon Sep 17 00:00:00 2001 From: Jake Shore Date: Wed, 11 Feb 2026 14:01:24 -0500 Subject: [PATCH] feat: add CloseBot MCP server source code (119 tools, 14 modules) --- .env.example | 1 + .gitignore | 4 + Dockerfile | 7 + README.md | 87 ++ package-lock.json | 1713 +++++++++++++++++++++++++++++++ package.json | 30 + railway.json | 4 + src/apps/analytics-dashboard.ts | 180 ++++ src/apps/bot-dashboard.ts | 135 +++ src/apps/lead-manager.ts | 184 ++++ src/apps/leaderboard.ts | 157 +++ src/apps/library-manager.ts | 192 ++++ src/apps/test-console.ts | 145 +++ src/client.ts | 210 ++++ src/index.ts | 156 +++ src/tools/agency-billing.ts | 353 +++++++ src/tools/analytics.ts | 344 +++++++ src/tools/bot-management.ts | 353 +++++++ src/tools/bot-testing.ts | 166 +++ src/tools/configuration.ts | 539 ++++++++++ src/tools/lead-management.ts | 157 +++ src/tools/library.ts | 232 +++++ src/tools/source-management.ts | 213 ++++ src/types.ts | 940 +++++++++++++++++ tsconfig.json | 20 + 25 files changed, 6522 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 railway.json create mode 100644 src/apps/analytics-dashboard.ts create mode 100644 src/apps/bot-dashboard.ts create mode 100644 src/apps/lead-manager.ts create mode 100644 src/apps/leaderboard.ts create mode 100644 src/apps/library-manager.ts create mode 100644 src/apps/test-console.ts create mode 100644 src/client.ts create mode 100644 src/index.ts create mode 100644 src/tools/agency-billing.ts create mode 100644 src/tools/analytics.ts create mode 100644 src/tools/bot-management.ts create mode 100644 src/tools/bot-testing.ts create mode 100644 src/tools/configuration.ts create mode 100644 src/tools/lead-management.ts create mode 100644 src/tools/library.ts create mode 100644 src/tools/source-management.ts create mode 100644 src/types.ts create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..16e22d6 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +CLOSEBOT_API_KEY=your_closebot_api_key_here diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa0926a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +.env +*.log diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..53c0707 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,7 @@ +FROM node:18-slim +WORKDIR /app +COPY package*.json ./ +RUN npm ci --production +COPY . . +RUN npm run build +CMD ["node", "dist/index.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..f188add --- /dev/null +++ b/README.md @@ -0,0 +1,87 @@ +# CloseBot MCP Server + +Full-featured MCP server for the [CloseBot](https://closebot.com) AI chatbot platform. Manage bots, leads, sources, analytics, knowledge base, and more — all from Claude Desktop or any MCP client. + +## Features + +- **119 tools** across 14 lazy-loaded modules +- **6 rich UI tool apps** with HTML dashboards +- **8 tool groups**: Bot Management, Source Management, Lead Management, Analytics & Metrics, Bot Testing, Library & Knowledge Base, Agency & Billing, Configuration +- **6 visual apps**: Bot Dashboard, Analytics Dashboard, Test Console, Lead Manager, Library Manager, Leaderboard +- Full TypeScript with types generated from CloseBot's OpenAPI spec +- Lazy-loaded modules for minimal context usage + +## Setup + +### 1. Install dependencies + +```bash +npm install +``` + +### 2. Build + +```bash +npm run build +``` + +### 3. Set your API key + +Get your API key from CloseBot's dashboard (Settings → API Keys). + +```bash +export CLOSEBOT_API_KEY=your_api_key_here +``` + +### 4. Add to Claude Desktop + +Add to your `claude_desktop_config.json`: + +```json +{ + "mcpServers": { + "closebot": { + "command": "node", + "args": ["/path/to/closebot-mcp/dist/index.js"], + "env": { + "CLOSEBOT_API_KEY": "your_api_key_here" + } + } + } +} +``` + +## Tool Groups + +| Group | Tools | Description | +|---|---|---| +| Bot Management | 18 | CRUD bots, AI creation, publish, versioning, templates, source attach | +| Source Management | 9 | Sources (GHL sub-accounts), calendars, channels, fields, tags | +| Lead Management | 6 | Search, filter, update leads and lead instances | +| Analytics & Metrics | 14 | Agency summary, booking graphs, leaderboards, message analytics, logs | +| Bot Testing | 7 | Test sessions with send/listen, force-step, rollback | +| Library & KB | 11 | Files, web-scraping, source attachment, content management | +| Agency & Billing | 18 | Billing, transactions, wallets, usage tracking, re-billing | +| Configuration | 30 | Personas, FAQs, folders, notifications, live demos, webhooks, API keys | + +## Tool Apps + +| App | Description | +|---|---| +| `bot_dashboard_app` | Grid view of all bots with status, versions, source count | +| `analytics_dashboard_app` | Agency stats, response/booking/revenue metrics with time range | +| `test_console_app` | Interactive test session viewer with conversation and controls | +| `lead_manager_app` | Searchable lead table with fields and conversation data | +| `library_manager_app` | File list with type indicators, sources, and scrape status | +| `leaderboard_app` | Global/local rankings by responses, bookings, or contacts | + +## Environment Variables + +| Variable | Required | Description | +|---|---|---| +| `CLOSEBOT_API_KEY` | Yes | Your CloseBot API key | +| `CLOSEBOT_BASE_URL` | No | Override API base URL (default: `https://api.closebot.com`) | + +## License + +MIT diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7d229bc --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1713 @@ +{ + "name": "closebot-mcp", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "closebot-mcp", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1" + }, + "bin": { + "closebot-mcp": "dist/index.js" + }, + "devDependencies": { + "@types/node": "^22.15.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@hono/node-server": { + "version": "1.19.9", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz", + "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==", + "license": "MIT", + "engines": { + "node": ">=18.14.1" + }, + "peerDependencies": { + "hono": "^4" + } + }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.25.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.25.3.tgz", + "integrity": "sha512-vsAMBMERybvYgKbg/l4L1rhS7VXV1c0CtyJg72vwxONVX0l4ZfKVAnZEWTQixJGTzKnELjQ59e4NbdFDALRiAQ==", + "license": "MIT", + "dependencies": { + "@hono/node-server": "^1.19.9", + "ajv": "^8.17.1", + "ajv-formats": "^3.0.1", + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "eventsource-parser": "^3.0.0", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "jose": "^6.1.1", + "json-schema-typed": "^8.0.2", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.25 || ^4.0", + "zod-to-json-schema": "^3.25.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@cfworker/json-schema": "^4.1.1", + "zod": "^3.25 || ^4.0" + }, + "peerDependenciesMeta": { + "@cfworker/json-schema": { + "optional": true + }, + "zod": { + "optional": false + } + } + }, + "node_modules/@types/node": { + "version": "22.19.8", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.8.tgz", + "integrity": "sha512-ebO/Yl+EAvVe8DnMfi+iaAyIqYdK0q/q0y0rw82INWEKJOBe6b/P3YWE8NW7oOlF/nXFNrHwhARrN/hdgDkraA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.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==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/body-parser": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", + "license": "MIT", + "peer": true, + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.1.tgz", + "integrity": "sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==", + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": ">= 4.11" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-tsconfig": { + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", + "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hono": { + "version": "4.11.7", + "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" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jose": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", + "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/json-schema-typed": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", + "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", + "license": "BSD-2-Clause" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pkce-challenge": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", + "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.7.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/serve-static": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsx": { + "version": "4.21.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz", + "integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "~0.27.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/zod": { + "version": "4.3.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" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", + "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "license": "ISC", + "peerDependencies": { + "zod": "^3.25 || ^4" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3763d33 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "closebot-mcp", + "version": "1.0.0", + "description": "MCP server for CloseBot AI chatbot platform API", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "closebot-mcp": "./dist/index.js" + }, + "scripts": { + "build": "tsc", + "start": "node dist/index.js", + "dev": "tsx src/index.ts", + "clean": "rm -rf dist", + "prepublishOnly": "npm run build" + }, + "keywords": ["mcp", "closebot", "ai", "chatbot"], + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.12.1" + }, + "devDependencies": { + "@types/node": "^22.15.0", + "tsx": "^4.19.0", + "typescript": "^5.7.0" + }, + "engines": { + "node": ">=18.0.0" + } +} diff --git a/railway.json b/railway.json new file mode 100644 index 0000000..be44d87 --- /dev/null +++ b/railway.json @@ -0,0 +1,4 @@ +{ + "build": { "builder": "NIXPACKS" }, + "deploy": { "startCommand": "npm start" } +} diff --git a/src/apps/analytics-dashboard.ts b/src/apps/analytics-dashboard.ts new file mode 100644 index 0000000..a7a715a --- /dev/null +++ b/src/apps/analytics-dashboard.ts @@ -0,0 +1,180 @@ +import { CloseBotClient, err } from "../client.js"; +import type { ToolDefinition, ToolResult, AgencyDashboardSummaryResponse } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "analytics_dashboard_app", + description: "Rich analytics dashboard showing agency summary stats, booking trends, and response/revenue metrics with time range support. Returns HTML visualization.", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "Optional source ID to filter metrics" }, + start: { type: "string", description: "Start date for booking graph (ISO 8601). Defaults to 30 days ago." }, + end: { type: "string", description: "End date for booking graph (ISO 8601). Defaults to now." }, + resolution: { type: "string", description: "Graph resolution: hourly, daily, monthly. Default: daily" }, + }, + }, + }, +]; + +function escapeHtml(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">"); +} + +function pctChange(current: number, last: number): string { + if (last === 0) return current > 0 ? "+∞" : "—"; + const pct = ((current - last) / last) * 100; + const sign = pct >= 0 ? "+" : ""; + const color = pct >= 0 ? "#4ecdc4" : "#ff6b6b"; + return `${sign}${pct.toFixed(1)}%`; +} + +function renderDashboard( + summary: AgencyDashboardSummaryResponse, + bookingData: unknown, + metricData: unknown +): string { + const cards = [ + { + label: "Responses This Month", + value: summary.currentMonthMessageCount ?? 0, + change: pctChange(summary.currentMonthMessageCount ?? 0, summary.lastMonthMessageCount ?? 0), + icon: "💬", + }, + { + label: "Bookings This Month", + value: summary.currentMonthSuccessfulBookings ?? 0, + change: pctChange(summary.currentMonthSuccessfulBookings ?? 0, summary.lastMonthSuccessfulBookings ?? 0), + icon: "📅", + }, + { + label: "Active Sources", + value: summary.currentMonthActiveSources ?? 0, + change: pctChange(summary.currentMonthActiveSources ?? 0, summary.lastMonthActiveSources ?? 0), + icon: "📡", + }, + { + label: "Contacts This Month", + value: summary.currentMonthContacts ?? 0, + change: pctChange(summary.currentMonthContacts ?? 0, summary.lastMonthContacts ?? 0), + icon: "👤", + }, + { + label: "Current Users", + value: summary.currentUsers ?? 0, + change: "", + icon: "👥", + }, + { + label: "Total Storage", + value: `${((summary.totalStorage ?? 0) / (1024 * 1024)).toFixed(1)} MB`, + change: "", + icon: "💾", + }, + ]; + + const cardHtml = cards + .map( + (c) => ` +
+
${c.icon}
+
${typeof c.value === "number" ? c.value.toLocaleString() : c.value}
+
${escapeHtml(c.label)}
+ ${c.change ? `
${c.change}
` : ""} +
` + ) + .join(""); + + // Render booking data as a simple text-based bar chart if it's an array + let bookingChartHtml = ""; + if (Array.isArray(bookingData) && bookingData.length > 0) { + const maxVal = Math.max(...bookingData.map((d: Record) => (d.count as number) || 0), 1); + const bars = bookingData + .slice(-14) // last 14 data points + .map((d: Record) => { + const val = (d.count as number) || 0; + const pct = (val / maxVal) * 100; + const label = d.label || d.date || d.key || ""; + return ` +
+
${escapeHtml(String(label))}
+
+
+
+
${val}
+
`; + }) + .join(""); + bookingChartHtml = ` +
+

📊 Booking Trend

+ ${bars} +
`; + } + + // Metric data summary + let metricHtml = ""; + if (metricData && typeof metricData === "object") { + metricHtml = ` +
+

📈 Response Metric Data

+
${escapeHtml(JSON.stringify(metricData, null, 2).slice(0, 2000))}
+
`; + } + + return ` +
+

📊 Analytics Dashboard

+
+ ${cardHtml} +
+ ${bookingChartHtml} + ${metricHtml} +
`; +} + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + const now = new Date(); + const thirtyDaysAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + const start = (args.start as string) || thirtyDaysAgo.toISOString(); + const end = (args.end as string) || now.toISOString(); + const resolution = (args.resolution as string) || "daily"; + + const [summary, bookingData, metricData] = await Promise.all([ + client.get("/botMetric/agencySummary", { + sourceId: args.sourceId, + }), + client.get("/botMetric/bookingGraph", { + start, + end, + resolution, + sourceId: args.sourceId, + }), + client.get("/botMetric/agencyMetric", { + metric: "responses", + start, + end, + resolution, + sourceId: args.sourceId, + }), + ]); + + const html = renderDashboard(summary, bookingData, metricData); + return { + content: [ + { + type: "text", + text: `Analytics: ${summary.currentMonthMessageCount} responses, ${summary.currentMonthSuccessfulBookings} bookings this month`, + }, + ], + structuredContent: { type: "html", html }, + }; + } catch (error) { + return err(error); + } +} diff --git a/src/apps/bot-dashboard.ts b/src/apps/bot-dashboard.ts new file mode 100644 index 0000000..e2aa020 --- /dev/null +++ b/src/apps/bot-dashboard.ts @@ -0,0 +1,135 @@ +import { CloseBotClient, err } from "../client.js"; +import type { ToolDefinition, ToolResult, BotDto } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "bot_dashboard_app", + description: "Rich dashboard showing all bots in a grid with status, versions, source count, and details. Returns HTML visualization.", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "Optional: show details for a specific bot instead of the grid" }, + }, + }, + }, +]; + +function escapeHtml(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +} + +function renderBotGrid(bots: BotDto[]): string { + const botCards = bots.map((bot) => { + const latestVersion = bot.versions?.find((v) => v.published) || bot.versions?.[0]; + const versionLabel = latestVersion?.version || "draft"; + const published = latestVersion?.published ? "🟢" : "🟡"; + const sourceCount = bot.sources?.length || 0; + const locked = bot.locked ? "🔒" : ""; + const fav = bot.favorited ? "⭐" : ""; + const category = bot.category || "—"; + const modified = bot.modifiedAt ? new Date(bot.modifiedAt).toLocaleDateString() : "—"; + + return ` +
+
+ ${fav} ${escapeHtml(bot.name || "Unnamed")} ${locked} + ${escapeHtml(category)} +
+
+ ${published} v${escapeHtml(versionLabel)} + 📡 ${sourceCount} source${sourceCount !== 1 ? "s" : ""} +
+
Modified: ${escapeHtml(modified)}
+
ID: ${escapeHtml(bot.id || "")}
+ ${bot.followUpActive ? '
↻ Follow-ups active
' : ""} + ${bot.tools && bot.tools.length > 0 ? `
🔧 ${bot.tools.length} tool(s)
` : ""} +
`; + }).join(""); + + return ` +
+

🤖 Bot Dashboard (${bots.length} bots)

+
+ ${botCards} +
+
`; +} + +function renderBotDetail(bot: BotDto): string { + const versions = (bot.versions || []) + .map( + (v) => + `${escapeHtml(v.version || "")}${v.published ? "✅ Published" : "📝 Draft"}${escapeHtml(v.name || "—")}${v.modifiedAt ? new Date(v.modifiedAt).toLocaleString() : "—"}` + ) + .join(""); + + const sources = (bot.sources || []) + .map( + (s) => + `${escapeHtml(s.name || "Unnamed")}${escapeHtml(s.category || "—")}${s.enabled ? "✅" : "❌"}${escapeHtml(s.id || "")}` + ) + .join(""); + + return ` +
+

🤖 ${escapeHtml(bot.name || "Unnamed")}

+
ID: ${escapeHtml(bot.id || "")}
+ +
+
+
${bot.sources?.length || 0}
+
Sources
+
+
+
${bot.versions?.length || 0}
+
Versions
+
+
+
${bot.locked ? "🔒" : "🔓"}
+
${bot.locked ? "Locked" : "Unlocked"}
+
+
+
${bot.followUpActive ? "✅" : "❌"}
+
Follow-ups
+
+
+ +

📋 Versions

+ + + ${versions || ''} +
VersionStatusNameModified
No versions
+ +

📡 Sources

+ + + ${sources || ''} +
NameCategoryEnabledID
No sources attached
+
`; +} + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + if (args.botId) { + const bot = await client.get(`/bot/${args.botId}`); + const html = renderBotDetail(bot); + return { + content: [{ type: "text", text: `Bot details for ${bot.name || bot.id}` }], + structuredContent: { type: "html", html }, + }; + } + + const bots = await client.get("/bot"); + const html = renderBotGrid(bots); + return { + content: [{ type: "text", text: `Dashboard showing ${bots.length} bots` }], + structuredContent: { type: "html", html }, + }; + } catch (error) { + return err(error); + } +} diff --git a/src/apps/lead-manager.ts b/src/apps/lead-manager.ts new file mode 100644 index 0000000..cc2b80e --- /dev/null +++ b/src/apps/lead-manager.ts @@ -0,0 +1,184 @@ +import { CloseBotClient, err } from "../client.js"; +import type { ToolDefinition, ToolResult, LeadDto, LeadDtoPaginated } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "lead_manager_app", + description: "Searchable lead table with fields, conversation snippets, and status indicators. Returns HTML visualization.", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "Filter by source ID" }, + page: { type: "number", description: "Page number (0-indexed)" }, + pageSize: { type: "number", description: "Page size (default 20, max 100)" }, + leadId: { type: "string", description: "Show details for a specific lead" }, + }, + }, + }, +]; + +function escapeHtml(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +} + +function renderLeadTable(data: LeadDtoPaginated): string { + const leads = data.results || []; + const rows = leads + .map((lead) => { + const lastMsg = lead.lastMessage + ? escapeHtml(lead.lastMessage.slice(0, 50)) + : ''; + const direction = + lead.lastMessageDirection === "outbound" + ? '→ out' + : lead.lastMessageDirection === "inbound" + ? '← in' + : ''; + const time = lead.lastMessageTime + ? new Date(lead.lastMessageTime).toLocaleString() + : "—"; + const source = lead.source?.name || "—"; + const fieldCount = lead.fields?.length || 0; + const instanceCount = lead.instances?.length || 0; + const failReason = lead.mostRecentFailureReason + ? `
⚠️ ${escapeHtml(lead.mostRecentFailureReason.slice(0, 40))}
` + : ""; + const tags = + lead.tags && lead.tags.length > 0 + ? lead.tags + .slice(0, 3) + .map((t) => `${escapeHtml(t)}`) + .join(" ") + : ""; + + return ` + + +
${escapeHtml(lead.name || "Unknown")}
+
${escapeHtml(lead.id || "")}
+ ${tags ? `
${tags}
` : ""} + + ${escapeHtml(source)} + + ${direction} +
${lastMsg}
+ ${failReason} + + ${escapeHtml(time)} + ${fieldCount} + ${instanceCount} + `; + }) + .join(""); + + return ` +
+

👥 Lead Manager

+
+ ${data.total ?? leads.length} total leads · Page ${(data.page ?? 0) + 1} · ${data.pageSize ?? 20} per page +
+
+ + + + + + + + + + + + + ${rows || ''} + +
LeadSourceLast MessageTimeFieldsBots
No leads found
+
+
`; +} + +function renderLeadDetail(lead: LeadDto): string { + const fields = (lead.fields || []) + .map( + (f) => + `${escapeHtml(f.field || "")}${escapeHtml(f.value || "—")}` + ) + .join(""); + + const instances = (lead.instances || []) + .map( + (i) => + `${escapeHtml(i.botId || "")}v${escapeHtml(i.botVersion || "?")}${i.followUpTime ? new Date(i.followUpTime).toLocaleString() : "—"}` + ) + .join(""); + + const tags = (lead.tags || []) + .map((t) => `${escapeHtml(t)}`) + .join(""); + + return ` +
+

👤 ${escapeHtml(lead.name || "Unknown Lead")}

+
${escapeHtml(lead.id || "")}
+ +
+
+
Source
+
${escapeHtml(lead.source?.name || "—")}
+
+
+
Last Message
+
${escapeHtml((lead.lastMessage || "—").slice(0, 80))}
+
+
+
Contact ID
+
${escapeHtml(lead.contactId || "—")}
+
+
+ + ${tags ? `
${tags}
` : ""} + + ${lead.mostRecentFailureReason ? `
⚠️ ${escapeHtml(lead.mostRecentFailureReason)}
` : ""} + +

📋 Fields (${lead.fields?.length || 0})

+ + ${fields || ''} +
No fields
+ +

🤖 Bot Instances (${lead.instances?.length || 0})

+ + + ${instances || ''} +
Bot IDVersionFollow-up
No instances
+
`; +} + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + if (args.leadId) { + const lead = await client.get(`/lead/${args.leadId}`); + const html = renderLeadDetail(lead); + return { + content: [{ type: "text", text: `Lead details: ${lead.name || lead.id}` }], + structuredContent: { type: "html", html }, + }; + } + + const data = await client.get("/lead", { + page: args.page, + pageSize: args.pageSize || 20, + sourceId: args.sourceId, + }); + const html = renderLeadTable(data); + return { + content: [{ type: "text", text: `${data.total ?? (data.results?.length || 0)} leads found` }], + structuredContent: { type: "html", html }, + }; + } catch (error) { + return err(error); + } +} diff --git a/src/apps/leaderboard.ts b/src/apps/leaderboard.ts new file mode 100644 index 0000000..3e07cde --- /dev/null +++ b/src/apps/leaderboard.ts @@ -0,0 +1,157 @@ +import { CloseBotClient, err } from "../client.js"; +import type { ToolDefinition, ToolResult, LeaderboardResponse } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "leaderboard_app", + description: + "Rich leaderboard visualization showing global and local rankings by metric (responses, bookings, contacts). Returns HTML.", + inputSchema: { + type: "object", + properties: { + metric: { + type: "string", + enum: ["responses", "bookings", "contacts"], + description: "Metric to rank by (default: responses)", + }, + scope: { + type: "string", + enum: ["global", "local"], + description: "Global or local leaderboard (default: global)", + }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + numTopLeaders: { + type: "integer", + description: "Number of top leaders to show (global, default 10)", + }, + numSurrounding: { + type: "integer", + description: "Number of surrounding agencies (local, default 5)", + }, + }, + }, + }, +]; + +function escapeHtml(s: string): string { + return s + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +function medalEmoji(rank: number): string { + if (rank === 1) return "🥇"; + if (rank === 2) return "🥈"; + if (rank === 3) return "🥉"; + return `#${rank}`; +} + +function renderLeaderboard( + entries: LeaderboardResponse[], + metric: string, + scope: string, + start?: string, + end?: string +): string { + const metricLabel = metric.charAt(0).toUpperCase() + metric.slice(1); + const scopeLabel = scope === "local" ? "Local" : "Global"; + const dateRange = + start && end + ? `${new Date(start).toLocaleDateString()} – ${new Date(end).toLocaleDateString()}` + : "All time"; + + const rows = entries + .map((e, i) => { + const rank = e.rank ?? i + 1; + const medal = medalEmoji(rank); + const name = escapeHtml(e.agencyName || e.agencyId || "Unknown"); + const value = e.value ?? 0; + const isYou = e.isCurrentAgency ? ' style="background:#2d3748;font-weight:bold"' : ""; + const youBadge = e.isCurrentAgency + ? ' YOU' + : ""; + return `${medal}${name}${youBadge}${value.toLocaleString()}`; + }) + .join("\n"); + + return `
+
+
+

${scopeLabel} Leaderboard

+ ${metricLabel} · ${dateRange} +
+
+ Responses + Bookings + Contacts +
+
+ + + + + + + + + + ${rows || ''} + +
RankAgency${metricLabel}
No leaderboard data available
+
${entries.length} agencies shown
+
`; +} + +export async function handle( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + const metric = (args.metric as string) || "responses"; + const scope = (args.scope as string) || "global"; + const start = args.start as string | undefined; + const end = args.end as string | undefined; + + let entries: LeaderboardResponse[]; + + if (scope === "local") { + entries = await client.get( + "/botMetric/localleaderboard", + { + metric, + start, + end, + numSurroundingAgencies: args.numSurrounding ?? 5, + } + ); + } else { + entries = await client.get( + "/botMetric/leaderboard", + { + metric, + start, + end, + numTopLeaders: args.numTopLeaders ?? 10, + } + ); + } + + const html = renderLeaderboard( + Array.isArray(entries) ? entries : [], + metric, + scope, + start, + end + ); + + return { + content: [{ type: "text" as const, text: html }], + }; + } catch (error) { + return err(error); + } +} diff --git a/src/apps/library-manager.ts b/src/apps/library-manager.ts new file mode 100644 index 0000000..b03e082 --- /dev/null +++ b/src/apps/library-manager.ts @@ -0,0 +1,192 @@ +import { CloseBotClient, err } from "../client.js"; +import type { ToolDefinition, ToolResult, FileDto } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "library_manager_app", + description: "Knowledge base library viewer showing files with type icons, source attachments, scrape status, and size. Returns HTML visualization.", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "Optional: show detail for a specific file including scrape pages" }, + }, + }, + }, +]; + +function escapeHtml(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +} + +function fileIcon(fileType: string | null | undefined): string { + const t = (fileType || "").toLowerCase(); + if (t.includes("pdf")) return "📄"; + if (t.includes("doc") || t.includes("word")) return "📝"; + if (t.includes("csv") || t.includes("excel") || t.includes("spreadsheet")) return "📊"; + if (t.includes("image") || t.includes("png") || t.includes("jpg")) return "🖼️"; + if (t.includes("webscrape") || t.includes("web") || t.includes("html")) return "🌐"; + if (t.includes("text") || t.includes("txt")) return "📃"; + if (t.includes("json")) return "🔧"; + return "📁"; +} + +function formatSize(bytes: number): string { + if (bytes < 1024) return `${bytes} B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; + return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; +} + +function statusBadge(status: string | null | undefined): string { + const s = (status || "").toLowerCase(); + if (s === "ready" || s === "completed" || s === "processed") + return '✅ Ready'; + if (s === "processing" || s === "pending") + return '⏳ Processing'; + if (s === "error" || s === "failed") + return '❌ Error'; + return `${escapeHtml(status || "Unknown")}`; +} + +function renderFileList(files: FileDto[]): string { + const totalSize = files.reduce((sum, f) => sum + (f.fileSize || 0), 0); + + const rows = files + .map((f) => { + const icon = fileIcon(f.fileType); + const sources = (f.sources || []) + .map( + (s) => + `${escapeHtml(s.name || s.id || "")}` + ) + .join(" "); + const modified = f.lastModified + ? new Date(f.lastModified).toLocaleDateString() + : "—"; + + return ` +
+
${icon}
+
+
+ ${escapeHtml(f.fileName || "Unnamed")} +
+
+ ${statusBadge(f.fileStatus)} + ${escapeHtml(f.fileType || "")} + ${formatSize(f.fileSize || 0)} + ${escapeHtml(modified)} +
+ ${sources ? `
${sources}
` : ""} +
+
${escapeHtml(f.fileId || "")}
+
`; + }) + .join(""); + + return ` +
+

📚 Library Manager

+
+ ${files.length} files · ${formatSize(totalSize)} total +
+
+ ${rows || '
No files. Use upload_file or create_web_scrape to add content.
'} +
+
+ 💡 Use upload_file to upload, create_web_scrape to scrape websites, attach_file_to_source to connect to sources +
+
`; +} + +function renderFileDetail(file: FileDto, scrapePages: Array<{ url?: string; enabled?: boolean }>): string { + const sources = (file.sources || []) + .map( + (s) => + `
+ ${escapeHtml(s.name || "Unnamed")} + ${escapeHtml(s.category || "")} · ${escapeHtml(s.id || "")} +
` + ) + .join(""); + + const pages = scrapePages + .map( + (p) => + `
+ ${p.enabled ? "✅" : "❌"} + ${escapeHtml(p.url || "")} +
` + ) + .join(""); + + return ` +
+
+ ${fileIcon(file.fileType)} +
+

${escapeHtml(file.fileName || "Unnamed")}

+
${escapeHtml(file.fileId || "")}
+
+
+ +
+
+
Status
+
${statusBadge(file.fileStatus)}
+
+
+
Type
+
${escapeHtml(file.fileType || "—")}
+
+
+
Size
+
${formatSize(file.fileSize || 0)}
+
+
+
Modified
+
${file.lastModified ? new Date(file.lastModified).toLocaleDateString() : "—"}
+
+
+ +

📡 Attached Sources (${file.sources?.length || 0})

+
+ ${sources || '
Not attached to any sources
'} +
+ + ${scrapePages.length > 0 ? ` +

🌐 Scrape Pages (${scrapePages.length})

+
+ ${pages} +
+ ` : ""} +
`; +} + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + if (args.fileId) { + const [file, scrapePages] = await Promise.all([ + client.get(`/library/files/${args.fileId}`), + client.get>(`/library/files/${args.fileId}/scrape-pages`).catch(() => []), + ]); + const html = renderFileDetail(file, scrapePages); + return { + content: [{ type: "text", text: `File: ${file.fileName} (${file.fileType})` }], + structuredContent: { type: "html", html }, + }; + } + + const files = await client.get("/library/files"); + const html = renderFileList(files); + return { + content: [{ type: "text", text: `Library: ${files.length} files` }], + structuredContent: { type: "html", html }, + }; + } catch (error) { + return err(error); + } +} diff --git a/src/apps/test-console.ts b/src/apps/test-console.ts new file mode 100644 index 0000000..55f7390 --- /dev/null +++ b/src/apps/test-console.ts @@ -0,0 +1,145 @@ +import { CloseBotClient, err } from "../client.js"; +import type { ToolDefinition, ToolResult, BotMetricMessage, ListLeadDto } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "test_console_app", + description: "Interactive test session viewer showing conversation messages, current step, and session list. Returns HTML visualization.", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "The bot ID" }, + leadId: { type: "string", description: "Optional: specific test session lead ID to view conversation" }, + }, + required: ["botId"], + }, + }, +]; + +function escapeHtml(s: string): string { + return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); +} + +function renderSessionList(sessions: ListLeadDto, botId: string): string { + const leads = sessions.leads || []; + const rows = leads + .map((lead) => { + const lastMsg = lead.lastMessage ? escapeHtml(lead.lastMessage.slice(0, 60)) : "—"; + const time = lead.lastMessageTime + ? new Date(lead.lastMessageTime).toLocaleString() + : "—"; + const direction = lead.lastMessageDirection === "outbound" ? "🤖→" : "←👤"; + + return ` +
+
+ ${escapeHtml(lead.name || "Test Lead")} + ${time} +
+
${direction} ${lastMsg}${(lead.lastMessage?.length ?? 0) > 60 ? "…" : ""}
+
Lead: ${escapeHtml(lead.id || "")}
+
`; + }) + .join(""); + + return ` +
+

🧪 Test Console

+
Bot: ${escapeHtml(botId)} · ${sessions.total ?? leads.length} session(s)
+
+ ${rows || '
No test sessions found. Create one with create_test_session.
'} +
+
`; +} + +function renderConversation(messages: BotMetricMessage[], botId: string, leadId: string): string { + const sorted = [...messages].sort( + (a, b) => new Date(a.timestamp || 0).getTime() - new Date(b.timestamp || 0).getTime() + ); + + const msgHtml = sorted + .map((msg) => { + const isBot = msg.fromBot || msg.direction === "outbound"; + const align = isBot ? "flex-end" : "flex-start"; + const bg = isBot ? "#1a3a5c" : "#2a1a3e"; + const border = isBot ? "#2a5a8c" : "#4a2a6e"; + const label = isBot ? "🤖 Bot" : "👤 User"; + const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : ""; + const content = msg.message || "[no content]"; + const activities = + msg.activities && msg.activities.length > 0 + ? `
Activities: ${msg.activities.map((a) => escapeHtml(a.activity || "")).join(", ")}
` + : ""; + const attachments = + msg.attachments && msg.attachments.length > 0 + ? `
📎 ${msg.attachments.length} attachment(s)
` + : ""; + + return ` +
+
+
+ ${label} + ${escapeHtml(time)} +
+
${escapeHtml(content)}
+ ${activities}${attachments} +
msg: ${escapeHtml(msg.messageId || "")}
+
+
`; + }) + .join(""); + + return ` +
+

🧪 Test Conversation

+
+ Bot: ${escapeHtml(botId)} · Lead: ${escapeHtml(leadId)} · ${sorted.length} messages +
+
+ ${msgHtml || '
No messages yet. Send one with send_test_message.
'} +
+
+ 💡 Use send_test_message to send messages + · force_test_step to advance + · rollback_test_session to undo +
+
`; +} + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + const botId = args.botId as string; + const leadId = args.leadId as string | undefined; + + if (leadId) { + const messages = await client.get("/botMetric/messages", { + leadId, + maxCount: 100, + }); + const html = renderConversation(messages, botId, leadId); + return { + content: [{ type: "text", text: `Test conversation: ${messages.length} messages` }], + structuredContent: { type: "html", html }, + }; + } + + const sessions = await client.get(`/bot/${botId}/testSession`, { + offset: 0, + maxCount: 50, + }); + const html = renderSessionList(sessions, botId); + return { + content: [ + { type: "text", text: `${sessions.total ?? (sessions.leads?.length || 0)} test sessions for bot ${botId}` }, + ], + structuredContent: { type: "html", html }, + }; + } catch (error) { + return err(error); + } +} diff --git a/src/client.ts b/src/client.ts new file mode 100644 index 0000000..9c6634a --- /dev/null +++ b/src/client.ts @@ -0,0 +1,210 @@ +// ============================================================================ +// CloseBot API HTTP Client +// ============================================================================ + +const BASE_URL = "https://api.closebot.com"; + +export class CloseBotClient { + private apiKey: string; + private baseUrl: string; + + constructor(apiKey?: string, baseUrl?: string) { + this.apiKey = apiKey || process.env.CLOSEBOT_API_KEY || ""; + this.baseUrl = baseUrl || process.env.CLOSEBOT_BASE_URL || BASE_URL; + + if (!this.apiKey) { + throw new Error( + "CloseBot API key is required. Set CLOSEBOT_API_KEY environment variable." + ); + } + } + + private buildUrl(path: string, query?: Record): string { + const url = new URL(path, this.baseUrl); + if (query) { + for (const [key, value] of Object.entries(query)) { + if (value !== undefined && value !== null && value !== "") { + url.searchParams.set(key, String(value)); + } + } + } + return url.toString(); + } + + private get headers(): Record { + return { + "X-CB-KEY": this.apiKey, + "Content-Type": "application/json", + Accept: "application/json", + }; + } + + async get( + path: string, + query?: Record + ): Promise { + const url = this.buildUrl(path, query); + const response = await fetch(url, { + method: "GET", + headers: this.headers, + }); + return this.handleResponse(response); + } + + async post( + path: string, + body?: unknown, + query?: Record + ): Promise { + const url = this.buildUrl(path, query); + const options: RequestInit = { + method: "POST", + headers: this.headers, + }; + if (body !== undefined) { + options.body = JSON.stringify(body); + } + const response = await fetch(url, options); + return this.handleResponse(response); + } + + async put( + path: string, + body?: unknown, + query?: Record + ): Promise { + const url = this.buildUrl(path, query); + const options: RequestInit = { + method: "PUT", + headers: this.headers, + }; + if (body !== undefined) { + options.body = JSON.stringify(body); + } + const response = await fetch(url, options); + return this.handleResponse(response); + } + + async delete( + path: string, + query?: Record + ): Promise { + const url = this.buildUrl(path, query); + const response = await fetch(url, { + method: "DELETE", + headers: this.headers, + }); + return this.handleResponse(response); + } + + async postFormData( + path: string, + formData: FormData, + query?: Record + ): Promise { + const url = this.buildUrl(path, query); + const headers: Record = { + "X-CB-KEY": this.apiKey, + Accept: "application/json", + }; + const response = await fetch(url, { + method: "POST", + headers, + body: formData, + }); + return this.handleResponse(response); + } + + async putFormData( + path: string, + formData: FormData, + query?: Record + ): Promise { + const url = this.buildUrl(path, query); + const headers: Record = { + "X-CB-KEY": this.apiKey, + Accept: "application/json", + }; + const response = await fetch(url, { + method: "PUT", + headers, + body: formData, + }); + return this.handleResponse(response); + } + + private async handleResponse(response: Response): Promise { + if (!response.ok) { + let errorBody: string; + try { + errorBody = await response.text(); + } catch { + errorBody = "Unable to read error body"; + } + throw new ApiError( + `CloseBot API error ${response.status}: ${response.statusText}`, + response.status, + errorBody + ); + } + + const contentType = response.headers.get("content-type"); + if (!contentType || response.status === 204) { + return {} as T; + } + + if (contentType.includes("application/json") || contentType.includes("text/json")) { + return (await response.json()) as T; + } + + const text = await response.text(); + try { + return JSON.parse(text) as T; + } catch { + return text as unknown as T; + } + } +} + +export class ApiError extends Error { + public statusCode: number; + public responseBody: string; + + constructor(message: string, statusCode: number, responseBody: string) { + super(message); + this.name = "ApiError"; + this.statusCode = statusCode; + this.responseBody = responseBody; + } +} + +/** Format API result as MCP text content */ +export function ok(data: unknown): { + content: Array<{ type: "text"; text: string }>; +} { + return { + content: [ + { + type: "text" as const, + text: typeof data === "string" ? data : JSON.stringify(data, null, 2), + }, + ], + }; +} + +/** Format error as MCP error content */ +export function err(error: unknown): { + content: Array<{ type: "text"; text: string }>; + isError: boolean; +} { + const message = + error instanceof ApiError + ? `API Error ${error.statusCode}: ${error.message}\n${error.responseBody}` + : error instanceof Error + ? error.message + : String(error); + return { + content: [{ type: "text" as const, text: message }], + isError: true, + }; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..35f56df --- /dev/null +++ b/src/index.ts @@ -0,0 +1,156 @@ +#!/usr/bin/env node +// ============================================================================ +// CloseBot MCP Server — Main Entry Point +// Lazy-loaded tool groups, 6 rich UI tool apps, ~55 tools +// ============================================================================ + +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, +} from "@modelcontextprotocol/sdk/types.js"; +import { CloseBotClient } from "./client.js"; +import type { ToolDefinition, ToolResult } from "./types.js"; + +// --------------------------------------------------------------------------- +// Lazy-loaded module registry +// --------------------------------------------------------------------------- + +interface ToolModule { + tools: ToolDefinition[]; + handle: ( + client: CloseBotClient, + name: string, + args: Record + ) => Promise; +} + +interface LazyGroup { + /** File path (relative) for dynamic import */ + path: string; + /** Cached module after first load */ + module?: ToolModule; + /** Tool metadata — populated eagerly, handler loaded lazily */ + toolNames: string[]; +} + +// Tool groups — we import metadata eagerly but handler code lazily +const groups: LazyGroup[] = [ + { path: "./tools/bot-management.js", toolNames: [] }, + { path: "./tools/source-management.js", toolNames: [] }, + { path: "./tools/lead-management.js", toolNames: [] }, + { path: "./tools/analytics.js", toolNames: [] }, + { path: "./tools/bot-testing.js", toolNames: [] }, + { path: "./tools/library.js", toolNames: [] }, + { path: "./tools/agency-billing.js", toolNames: [] }, + { path: "./tools/configuration.js", toolNames: [] }, + // Apps + { path: "./apps/bot-dashboard.js", toolNames: [] }, + { path: "./apps/analytics-dashboard.js", toolNames: [] }, + { path: "./apps/test-console.js", toolNames: [] }, + { path: "./apps/lead-manager.js", toolNames: [] }, + { path: "./apps/library-manager.js", toolNames: [] }, + { path: "./apps/leaderboard.js", toolNames: [] }, +]; + +// Map: toolName → group index (for fast dispatch) +const toolToGroup = new Map(); +// All tool definitions (populated on init) +let allTools: ToolDefinition[] = []; + +async function loadGroupMetadata(): Promise { + const toolDefs: ToolDefinition[] = []; + for (let i = 0; i < groups.length; i++) { + const mod = (await import(groups[i].path)) as ToolModule; + groups[i].module = mod; + groups[i].toolNames = mod.tools.map((t) => t.name); + for (const tool of mod.tools) { + toolToGroup.set(tool.name, i); + toolDefs.push(tool); + } + } + allTools = toolDefs; +} + +async function getHandler( + toolName: string +): Promise { + const idx = toolToGroup.get(toolName); + if (idx === undefined) return null; + const group = groups[idx]; + if (!group.module) { + group.module = (await import(group.path)) as ToolModule; + } + return group.module.handle; +} + +// --------------------------------------------------------------------------- +// Server setup +// --------------------------------------------------------------------------- + +async function main(): Promise { + // Init client (validates API key) + let client: CloseBotClient; + try { + client = new CloseBotClient(); + } catch (e) { + console.error( + (e as Error).message || + "Failed to initialize. Set CLOSEBOT_API_KEY env var." + ); + process.exit(1); + } + + // Load all tool metadata + await loadGroupMetadata(); + + const server = new Server( + { + name: "closebot-mcp", + version: "1.0.0", + }, + { + capabilities: { + tools: {}, + }, + } + ); + + // --- List Tools --- + server.setRequestHandler(ListToolsRequestSchema, async () => { + return { + tools: allTools.map((t) => ({ + name: t.name, + description: t.description, + inputSchema: t.inputSchema, + })), + }; + }); + + // --- Call Tool --- + server.setRequestHandler(CallToolRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + const handler = await getHandler(name); + if (!handler) { + return { + content: [{ type: "text" as const, text: `Unknown tool: ${name}` }], + isError: true, + } as Record; + } + const result = await handler(client, name, (args as Record) || {}); + return result as unknown as Record; + }); + + // --- Connect stdio transport --- + const transport = new StdioServerTransport(); + await server.connect(transport); + console.error( + `CloseBot MCP server running — ${allTools.length} tools loaded across ${groups.length} modules` + ); +} + +main().catch((err) => { + console.error("Fatal error:", err); + process.exit(1); +}); diff --git a/src/tools/agency-billing.ts b/src/tools/agency-billing.ts new file mode 100644 index 0000000..97d5f6a --- /dev/null +++ b/src/tools/agency-billing.ts @@ -0,0 +1,353 @@ +import { CloseBotClient, ok, err } from "../client.js"; +import type { ToolDefinition, ToolResult, AgencyDto, BalanceDto, BillingOptionsDto, UpdateBillingConfigInput, ReBillingDto, ReBillingUpdateInput, TransactionDto, BilledUsageDto, AddSourceTransactionDto } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "get_agency", + description: "Get the current agency details", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "list_agencies", + description: "List all agencies the account is part of (currently max 1)", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "get_usage", + description: "Get agency usage across scopes: bots, storage, users, responses", + inputSchema: { + type: "object", + properties: { + scopes: { + type: "string", + description: "Comma-separated scopes (default: bots,storage,users,responses)", + }, + }, + }, + }, + { + name: "invite_users", + description: "Invite users to the agency", + inputSchema: { + type: "object", + properties: { + invites: { + type: "array", + description: "Array of invite objects", + items: { + type: "object", + properties: { + email: { type: "string", description: "Email to invite" }, + role: { type: "string", description: "Role (admin, member, etc.)" }, + sourceIds: { type: "array", items: { type: "string" }, description: "Source IDs to give access" }, + }, + }, + }, + }, + required: ["invites"], + }, + }, + { + name: "revoke_invitation", + description: "Revoke an invitation by email", + inputSchema: { + type: "object", + properties: { + email: { type: "string", description: "The email to revoke invitation for" }, + }, + required: ["email"], + }, + }, + { + name: "get_balance", + description: "Get the agency wallet balance", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "get_source_balance", + description: "Get the balance for a specific source", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "The source ID" }, + }, + required: ["sourceId"], + }, + }, + { + name: "get_billing_options", + description: "Get agency billing configuration (auto-refill, thresholds, etc.)", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "update_billing", + description: "Update agency billing configuration", + inputSchema: { + type: "object", + properties: { + overBillingEnabled: { type: "boolean", description: "Enable over-billing" }, + autoRefillEnabled: { type: "boolean", description: "Enable auto-refill" }, + topUpAmount: { type: "number", description: "Top-up amount (smallest currency unit)" }, + refillThreshold: { type: "number", description: "Refill threshold (smallest currency unit)" }, + }, + }, + }, + { + name: "list_transactions", + description: "List agency transactions with optional status filter and pagination", + inputSchema: { + type: "object", + properties: { + status: { type: "string", description: "Filter by status: pending, succeeded, failed" }, + page: { type: "number", description: "Page number (default 1)" }, + pageSize: { type: "number", description: "Page size (default 20)" }, + }, + }, + }, + { + name: "list_source_transactions", + description: "List transactions for a specific source", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "The source ID" }, + status: { type: "string", description: "Filter: pending, succeeded, failed" }, + page: { type: "number", description: "Page number" }, + pageSize: { type: "number", description: "Page size" }, + }, + required: ["sourceId"], + }, + }, + { + name: "add_source_transaction", + description: "Add a manual transaction to a source wallet", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "The source ID" }, + amount: { type: "string", description: "Transaction amount" }, + description: { type: "string", description: "Transaction description" }, + }, + required: ["sourceId", "amount"], + }, + }, + { + name: "delete_source_transaction", + description: "Delete a source transaction", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "The source ID" }, + id: { type: "number", description: "Transaction ID" }, + }, + required: ["sourceId", "id"], + }, + }, + { + name: "list_billed_usages", + description: "List billed usage records for a time range", + inputSchema: { + type: "object", + properties: { + startTime: { type: "string", description: "Start time (ISO 8601)" }, + endTime: { type: "string", description: "End time (ISO 8601)" }, + }, + }, + }, + { + name: "get_rebilling", + description: "Get re-billing settings (response, storage, user costs and token multiplier)", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "update_rebilling", + description: "Update re-billing settings", + inputSchema: { + type: "object", + properties: { + enabled: { type: "boolean", description: "Enable re-billing" }, + responseCost: { type: "string", description: "Response unit cost" }, + storageCost: { type: "string", description: "Storage unit cost" }, + userCost: { type: "string", description: "User unit cost" }, + tokenMultiplier: { type: "string", description: "Token multiplier" }, + }, + }, + }, + { + name: "refill_agency_wallet", + description: "⚠️ Charges your payment method to refill the agency wallet. Use with caution.", + inputSchema: { + type: "object", + properties: { + amount: { type: "number", description: "Amount to refill (smallest currency unit)" }, + currency: { type: "string", description: "Currency code (e.g., usd)" }, + }, + required: ["amount"], + }, + }, + { + name: "refill_source_wallet", + description: "⚠️ Charges your payment method to refill a source wallet. Use with caution.", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "The source ID" }, + amount: { type: "number", description: "Amount to refill" }, + currency: { type: "string", description: "Currency code" }, + }, + required: ["sourceId", "amount"], + }, + }, +]; + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + switch (name) { + case "get_agency": + return ok(await client.get("/agency/current")); + + case "list_agencies": + return ok(await client.get("/agency")); + + case "get_usage": + return ok( + await client.get("/agency/usage", { scopes: args.scopes }) + ); + + case "invite_users": + return ok( + await client.post("/agency/invite", { invites: args.invites }) + ); + + case "revoke_invitation": + return ok( + await client.delete("/agency/invite", { email: args.email }) + ); + + case "get_balance": + return ok(await client.get("/agency/billing/balance")); + + case "get_source_balance": + return ok( + await client.get( + `/agency/billing/balance/source/${args.sourceId}` + ) + ); + + case "get_billing_options": + return ok( + await client.get("/agency/billing/options") + ); + + case "update_billing": { + const body: UpdateBillingConfigInput = {}; + if (args.overBillingEnabled !== undefined) + body.overBillingEnabled = args.overBillingEnabled as boolean; + if (args.autoRefillEnabled !== undefined) + body.autoRefillEnabled = args.autoRefillEnabled as boolean; + if (args.topUpAmount !== undefined) + body.topUpAmount = args.topUpAmount as number; + if (args.refillThreshold !== undefined) + body.refillThreshold = args.refillThreshold as number; + return ok( + await client.put("/agency/billing/options", body) + ); + } + + case "list_transactions": + return ok( + await client.get("/agency/billing/transactions", { + status: args.status, + page: args.page, + pageSize: args.pageSize, + }) + ); + + case "list_source_transactions": + return ok( + await client.get( + `/agency/billing/transactions/source/${args.sourceId}`, + { + status: args.status, + page: args.page, + pageSize: args.pageSize, + } + ) + ); + + case "add_source_transaction": { + const body: AddSourceTransactionDto = { + amount: args.amount as string, + description: args.description as string | undefined, + }; + return ok( + await client.post( + `/agency/billing/transactions/source/${args.sourceId}`, + body + ) + ); + } + + case "delete_source_transaction": + return ok( + await client.delete( + `/agency/billing/transactions/source/${args.sourceId}/${args.id}` + ) + ); + + case "list_billed_usages": + return ok( + await client.get("/agency/billing/usages", { + startTime: args.startTime, + endTime: args.endTime, + }) + ); + + case "get_rebilling": + return ok( + await client.get("/agency/billing/re-billing") + ); + + case "update_rebilling": { + const body: ReBillingUpdateInput = {}; + if (args.enabled !== undefined) body.enabled = args.enabled as boolean; + if (args.responseCost !== undefined) + body.responseCost = args.responseCost as string; + if (args.storageCost !== undefined) + body.storageCost = args.storageCost as string; + if (args.userCost !== undefined) + body.userCost = args.userCost as string; + if (args.tokenMultiplier !== undefined) + body.tokenMultiplier = args.tokenMultiplier as string; + return ok( + await client.put("/agency/billing/re-billing", body) + ); + } + + case "refill_agency_wallet": + return ok( + await client.post("/agency/billing/refill", { + amount: args.amount, + currency: args.currency, + }) + ); + + case "refill_source_wallet": + return ok( + await client.post( + `/agency/billing/refill/source/${args.sourceId}`, + { amount: args.amount, currency: args.currency } + ) + ); + + default: + return err(`Unknown tool: ${name}`); + } + } catch (error) { + return err(error); + } +} diff --git a/src/tools/analytics.ts b/src/tools/analytics.ts new file mode 100644 index 0000000..a53f386 --- /dev/null +++ b/src/tools/analytics.ts @@ -0,0 +1,344 @@ +import { CloseBotClient, ok, err } from "../client.js"; +import type { ToolDefinition, ToolResult, AgencyDashboardSummaryResponse, BotMetricAction, BotMetricMessage, BotMetricLog, LeaderboardResponse, MessageFeedbackRequest, MessageFeedbackResponse, MessageLikesResponse, MessageReason } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "get_agency_summary", + description: "Get agency dashboard summary with message counts, bookings, sources, contacts, and storage", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "Optional source ID to filter by" }, + }, + }, + }, + { + name: "get_agency_metric", + description: "Get a specific agency metric over time. Metrics: responses, bookings, activeSources, contacts, totalStorage, revenue", + inputSchema: { + type: "object", + properties: { + metric: { type: "string", description: "Metric name: responses, bookings, activeSources, contacts, totalStorage, revenue" }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + resolution: { type: "string", description: "Time resolution (hourly, daily, monthly)" }, + sourceId: { type: "string", description: "Optional source ID" }, + }, + required: ["metric", "start", "end"], + }, + }, + { + name: "get_booking_graph", + description: "Get booking graph data over a time range", + inputSchema: { + type: "object", + properties: { + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + resolution: { type: "string", description: "Resolution: hourly, daily, or monthly" }, + sourceId: { type: "string", description: "Optional source ID" }, + }, + required: ["start", "end"], + }, + }, + { + name: "get_action_count", + description: "Get the count of bot actions, optionally filtered by lead, source, bot, and date range", + inputSchema: { + type: "object", + properties: { + leadId: { type: "string", description: "Optional lead ID" }, + sourceId: { type: "string", description: "Optional source ID" }, + botId: { type: "string", description: "Optional bot ID" }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + }, + }, + }, + { + name: "get_actions", + description: "Get detailed bot actions with optional filters", + inputSchema: { + type: "object", + properties: { + leadId: { type: "string", description: "Optional lead ID" }, + sourceId: { type: "string", description: "Optional source ID" }, + botId: { type: "string", description: "Optional bot ID" }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + maxCount: { type: "number", description: "Maximum number of actions to return" }, + }, + }, + }, + { + name: "get_message_count", + description: "Get message count, optionally filtered by source, lead, and date range", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "Optional source ID" }, + leadId: { type: "string", description: "Optional lead ID" }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + }, + }, + }, + { + name: "get_messages", + description: "Get messages with optional filters. Returns message content, direction, attachments, and activities.", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "Optional source ID" }, + leadId: { type: "string", description: "Optional lead ID" }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + maxCount: { type: "number", description: "Maximum messages to return" }, + }, + }, + }, + { + name: "get_message_feedback", + description: "Get message IDs that have feedback for a lead", + inputSchema: { + type: "object", + properties: { + leadId: { type: "string", description: "The lead ID" }, + }, + required: ["leadId"], + }, + }, + { + name: "save_message_feedback", + description: "Save feedback (like/dislike with reasons) for a specific message", + inputSchema: { + type: "object", + properties: { + messageId: { type: "string", description: "The message ID" }, + leadId: { type: "string", description: "The lead ID" }, + liked: { type: "boolean", description: "Whether the message was liked" }, + reasons: { type: "string", description: "Feedback reasons" }, + }, + required: ["messageId", "leadId"], + }, + }, + { + name: "get_message_likes", + description: "Get message IDs that have likes for a lead", + inputSchema: { + type: "object", + properties: { + leadId: { type: "string", description: "The lead ID" }, + }, + required: ["leadId"], + }, + }, + { + name: "get_message_reason", + description: "Get the feedback reason for a specific message", + inputSchema: { + type: "object", + properties: { + messageId: { type: "string", description: "The message ID" }, + }, + required: ["messageId"], + }, + }, + { + name: "get_leaderboard", + description: "Get the global leaderboard. Metrics: responses, bookings, contacts", + inputSchema: { + type: "object", + properties: { + metric: { type: "string", description: "Metric: responses, bookings, contacts" }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + numTopLeaders: { type: "number", description: "Number of top leaders to return" }, + }, + required: ["metric", "start", "end"], + }, + }, + { + name: "get_local_leaderboard", + description: "Get the local leaderboard (your agency + surrounding). Metrics: responses, bookings, contacts", + inputSchema: { + type: "object", + properties: { + metric: { type: "string", description: "Metric: responses, bookings, contacts" }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + numSurroundingAgencies: { type: "number", description: "Number of surrounding agencies" }, + }, + required: ["metric", "start", "end"], + }, + }, + { + name: "get_logs", + description: "Get bot execution logs with optional filters. Includes prompts, tokens, model info.", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "Optional bot ID" }, + messageId: { type: "string", description: "Optional message ID" }, + sourceId: { type: "string", description: "Optional source ID" }, + leadId: { type: "string", description: "Optional lead ID" }, + actionId: { type: "string", description: "Optional action ID" }, + start: { type: "string", description: "Start date (ISO 8601)" }, + end: { type: "string", description: "End date (ISO 8601)" }, + maxCount: { type: "number", description: "Maximum logs to return" }, + }, + }, + }, +]; + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + switch (name) { + case "get_agency_summary": + return ok( + await client.get("/botMetric/agencySummary", { + sourceId: args.sourceId, + }) + ); + + case "get_agency_metric": + return ok( + await client.get("/botMetric/agencyMetric", { + metric: args.metric, + start: args.start, + end: args.end, + resolution: args.resolution, + sourceId: args.sourceId, + }) + ); + + case "get_booking_graph": + return ok( + await client.get("/botMetric/bookingGraph", { + start: args.start, + end: args.end, + resolution: args.resolution, + sourceId: args.sourceId, + }) + ); + + case "get_action_count": + return ok( + await client.get("/botMetric/actionCount", { + leadId: args.leadId, + sourceId: args.sourceId, + botId: args.botId, + start: args.start, + end: args.end, + }) + ); + + case "get_actions": + return ok( + await client.get("/botMetric/actions", { + leadId: args.leadId, + sourceId: args.sourceId, + botId: args.botId, + start: args.start, + end: args.end, + maxCount: args.maxCount, + }) + ); + + case "get_message_count": + return ok( + await client.get("/botMetric/messageCount", { + sourceId: args.sourceId, + leadId: args.leadId, + start: args.start, + end: args.end, + }) + ); + + case "get_messages": + return ok( + await client.get("/botMetric/messages", { + sourceId: args.sourceId, + leadId: args.leadId, + start: args.start, + end: args.end, + maxCount: args.maxCount, + }) + ); + + case "get_message_feedback": + return ok( + await client.get("/botMetric/messageFeedback", { + leadId: args.leadId, + }) + ); + + case "save_message_feedback": { + const body: MessageFeedbackRequest = { + messageId: args.messageId as string, + leadId: args.leadId as string, + liked: args.liked as boolean | undefined, + reasons: args.reasons as string | undefined, + }; + return ok(await client.post("/botMetric/messageFeedback", body)); + } + + case "get_message_likes": + return ok( + await client.get("/botMetric/messageLikes", { + leadId: args.leadId, + }) + ); + + case "get_message_reason": + return ok( + await client.get("/botMetric/messageReason", { + messageId: args.messageId, + }) + ); + + case "get_leaderboard": + return ok( + await client.get("/botMetric/leaderboard", { + metric: args.metric, + start: args.start, + end: args.end, + numTopLeaders: args.numTopLeaders, + }) + ); + + case "get_local_leaderboard": + return ok( + await client.get("/botMetric/localleaderboard", { + metric: args.metric, + start: args.start, + end: args.end, + numSurroundingAgencies: args.numSurroundingAgencies, + }) + ); + + case "get_logs": + return ok( + await client.get("/botMetric/logs", { + botId: args.botId, + messageId: args.messageId, + sourceId: args.sourceId, + leadId: args.leadId, + actionId: args.actionId, + start: args.start, + end: args.end, + maxCount: args.maxCount, + }) + ); + + default: + return err(`Unknown tool: ${name}`); + } + } catch (error) { + return err(error); + } +} diff --git a/src/tools/bot-management.ts b/src/tools/bot-management.ts new file mode 100644 index 0000000..fc5a56c --- /dev/null +++ b/src/tools/bot-management.ts @@ -0,0 +1,353 @@ +import { CloseBotClient, ok, err } from "../client.js"; +import type { ToolDefinition, ToolResult, BotDto, CreateBotInput, AiCreateBotInput, UpdateBotInput, ExportBotResponse, SaveBotInput, SaveBotResponse, PublishBotResponse, ToolInputDto, BotToolDto, BotTemplateDto, UpdateVersionInput } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "list_bots", + description: "List all bots in the agency", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "get_bot", + description: "Get a specific bot by ID", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + }, + required: ["id"], + }, + }, + { + name: "create_bot", + description: "Create a new bot. Can use a template ID or import KDL.", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "The name of the new bot" }, + templateId: { type: "string", description: "Template ID to create from" }, + importKdl: { type: "string", description: "KDL template to import" }, + folderId: { type: "string", description: "Folder ID to place the bot in" }, + category: { type: "string", description: "Bot category (GHL, WebHook, etc.)" }, + }, + required: ["name"], + }, + }, + { + name: "create_bot_with_ai", + description: "Create a new bot using AI-generated steps from a text description", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "The name of the new bot" }, + description: { type: "string", description: "Prompt describing what the bot should do" }, + category: { type: "string", description: "Source category (GHL, WebHook, etc.)" }, + folderId: { type: "string", description: "Folder ID to place the bot in" }, + }, + required: ["name", "description"], + }, + }, + { + name: "update_bot", + description: "Update bot fields. Only non-null fields are updated.", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + name: { type: "string", description: "New name" }, + favorite: { type: "boolean", description: "Whether the bot is favorited" }, + trash: { type: "boolean", description: "Whether the bot is trashed" }, + locked: { type: "boolean", description: "Whether the bot is locked" }, + rescheduling: { type: "boolean", description: "Enable conversation rescheduling" }, + folderId: { type: "string", description: "Folder ID" }, + category: { type: "string", description: "Category" }, + followUpActive: { type: "boolean", description: "Enable follow-ups" }, + smartFollowUp: { type: "boolean", description: "Enable smart follow-ups" }, + followUpRepeat: { type: "boolean", description: "Repeat last follow-up sequence" }, + followUpVarianceMinutes: { type: "number", description: "Variance minutes for follow-ups" }, + followUpExtraPrompt: { type: "string", description: "Extra prompt for follow-ups" }, + }, + required: ["id"], + }, + }, + { + name: "delete_bot", + description: "Delete a bot by ID", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + }, + required: ["id"], + }, + }, + { + name: "duplicate_bot", + description: "Duplicate an existing bot", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID to duplicate" }, + }, + required: ["id"], + }, + }, + { + name: "export_bot", + description: "Export a bot as KDL. Optionally specify a version.", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + botVersion: { type: "string", description: "Version (x.y.z). Defaults to latest." }, + }, + required: ["id"], + }, + }, + { + name: "publish_bot", + description: "Publish a bot to make it live", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + }, + required: ["id"], + }, + }, + { + name: "save_bot", + description: "Save bot steps (the bot flow definition)", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + botSteps: { type: "object", description: "The bot steps/flow data" }, + }, + required: ["id", "botSteps"], + }, + }, + { + name: "save_bot_tools", + description: "Save the tools configuration for a bot", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + tools: { + type: "array", + description: "Array of tool configurations", + items: { + type: "object", + properties: { + type: { type: "string", description: "Tool type" }, + enabled: { type: "boolean", description: "Whether enabled" }, + options: { type: "object", description: "Tool options" }, + }, + }, + }, + }, + required: ["id", "tools"], + }, + }, + { + name: "get_bot_steps", + description: "Get the steps/flow for a specific bot version", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + botVersion: { type: "string", description: "Version (x.y.z)" }, + }, + required: ["id"], + }, + }, + { + name: "get_node_descriptors", + description: "Get all available node descriptors for the bot builder", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "update_bot_version", + description: "Update a specific bot version (rename or import KDL)", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + version: { type: "string", description: "The version ID" }, + name: { type: "string", description: "New version name" }, + importKdl: { type: "string", description: "KDL to import into this version" }, + }, + required: ["id", "version"], + }, + }, + { + name: "get_bot_templates", + description: "List all available bot templates", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "attach_source_to_bot", + description: "Attach a source to a bot with optional tag/channel config", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + sourceId: { type: "string", description: "The source ID" }, + enabled: { type: "boolean", description: "Whether the attachment is enabled" }, + channels: { type: "array", items: { type: "string" }, description: "Channel list" }, + personaNameOverride: { type: "string", description: "Persona name override" }, + }, + required: ["id", "sourceId"], + }, + }, + { + name: "detach_source_from_bot", + description: "Detach a source from a bot", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The bot ID" }, + sourceId: { type: "string", description: "The source ID" }, + }, + required: ["id", "sourceId"], + }, + }, + { + name: "get_bbb_templates", + description: "Get bot builder builder template names", + inputSchema: { type: "object", properties: {} }, + }, +]; + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + switch (name) { + case "list_bots": + return ok(await client.get("/bot")); + + case "get_bot": + return ok(await client.get(`/bot/${args.id}`)); + + case "create_bot": { + const body: CreateBotInput = { + name: args.name as string, + templateId: args.templateId as string | undefined, + importKdl: args.importKdl as string | undefined, + folderId: args.folderId as string | undefined, + category: args.category as string | undefined, + }; + return ok(await client.post("/bot", body)); + } + + case "create_bot_with_ai": { + const body: AiCreateBotInput = { + name: args.name as string, + description: args.description as string, + category: args.category as string | undefined, + folderId: args.folderId as string | undefined, + }; + return ok(await client.post("/bot/ai", body)); + } + + case "update_bot": { + const { id, ...rest } = args; + const body: UpdateBotInput = {}; + if (rest.name !== undefined) body.name = rest.name as string; + if (rest.favorite !== undefined) body.favorite = rest.favorite as boolean; + if (rest.trash !== undefined) body.trash = rest.trash as boolean; + if (rest.locked !== undefined) body.locked = rest.locked as boolean; + if (rest.rescheduling !== undefined) body.rescheduling = rest.rescheduling as boolean; + if (rest.folderId !== undefined) body.folderId = rest.folderId as string; + if (rest.category !== undefined) body.category = rest.category as string; + if (rest.followUpActive !== undefined) body.followUpActive = rest.followUpActive as boolean; + if (rest.smartFollowUp !== undefined) body.smartFollowUp = rest.smartFollowUp as boolean; + if (rest.followUpRepeat !== undefined) body.followUpRepeat = rest.followUpRepeat as boolean; + if (rest.followUpVarianceMinutes !== undefined) + body.followUpVarianceMinutes = rest.followUpVarianceMinutes as number; + if (rest.followUpExtraPrompt !== undefined) + body.followUpExtraPrompt = rest.followUpExtraPrompt as string; + return ok(await client.put(`/bot/${id}`, body)); + } + + case "delete_bot": + return ok(await client.delete(`/bot/${args.id}`)); + + case "duplicate_bot": + return ok(await client.post(`/bot/${args.id}/duplicate`)); + + case "export_bot": + return ok( + await client.get(`/bot/${args.id}/export`, { + botVersion: args.botVersion, + }) + ); + + case "publish_bot": + return ok(await client.post(`/bot/${args.id}/publish`)); + + case "save_bot": { + const body: SaveBotInput = { botSteps: args.botSteps }; + return ok(await client.post(`/bot/${args.id}/save`, body)); + } + + case "save_bot_tools": + return ok( + await client.post( + `/bot/${args.id}/saveTools`, + args.tools as ToolInputDto[] + ) + ); + + case "get_bot_steps": + return ok( + await client.get(`/bot/${args.id}/steps`, { + botVersion: args.botVersion, + }) + ); + + case "get_node_descriptors": + return ok(await client.get("/bot/nodeDescriptors")); + + case "update_bot_version": { + const body: UpdateVersionInput = {}; + if (args.name !== undefined) body.name = args.name as string; + if (args.importKdl !== undefined) body.importKdl = args.importKdl as string; + return ok( + await client.put(`/bot/${args.id}/version/${args.version}`, body) + ); + } + + case "get_bot_templates": + return ok(await client.get("/botTemplates")); + + case "attach_source_to_bot": { + const body: Record = {}; + if (args.enabled !== undefined) body.enabled = args.enabled; + if (args.channels !== undefined) body.channels = args.channels; + if (args.personaNameOverride !== undefined) + body.personaNameOverride = args.personaNameOverride; + return ok( + await client.post(`/bot/${args.id}/source/${args.sourceId}`, body) + ); + } + + case "detach_source_from_bot": + return ok( + await client.delete(`/bot/${args.id}/source/${args.sourceId}`) + ); + + case "get_bbb_templates": + return ok(await client.get("/bot/bbb/templates")); + + default: + return err(`Unknown tool: ${name}`); + } + } catch (error) { + return err(error); + } +} diff --git a/src/tools/bot-testing.ts b/src/tools/bot-testing.ts new file mode 100644 index 0000000..8a25042 --- /dev/null +++ b/src/tools/bot-testing.ts @@ -0,0 +1,166 @@ +import { CloseBotClient, ok, err } from "../client.js"; +import type { ToolDefinition, ToolResult, TestSession, TestSessionMessageInput, UpdateSessionInput, UpdateSessionDto, BotTestingRollbackInput, BotTestingRollbackOutput, ListLeadDto } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "create_test_session", + description: "Create a new test session for a bot. Returns a lead ID and source ID for the test.", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "The bot ID" }, + }, + required: ["botId"], + }, + }, + { + name: "list_test_sessions", + description: "List test sessions for a bot with pagination", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "The bot ID" }, + offset: { type: "number", description: "Offset (default 0)" }, + maxCount: { type: "number", description: "Max results (default 100)" }, + }, + required: ["botId"], + }, + }, + { + name: "delete_test_session", + description: "Delete a test session", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "The bot ID" }, + leadId: { type: "string", description: "The test session lead ID" }, + }, + required: ["botId", "leadId"], + }, + }, + { + name: "update_test_session", + description: "Update a test session (e.g., change mimic source)", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "The bot ID" }, + leadId: { type: "string", description: "The test session lead ID" }, + mimicSourceId: { type: "string", description: "Source ID to mimic" }, + }, + required: ["botId", "leadId"], + }, + }, + { + name: "send_test_message", + description: "Send a message to a test session and get bot response", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "The bot ID" }, + leadId: { type: "string", description: "The test session lead ID" }, + message: { type: "string", description: "The message to send" }, + }, + required: ["botId", "leadId", "message"], + }, + }, + { + name: "force_test_step", + description: "Force the bot to process the next step in a test session", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "The bot ID" }, + leadId: { type: "string", description: "The test session lead ID" }, + }, + required: ["botId", "leadId"], + }, + }, + { + name: "rollback_test_session", + description: "Rollback a test session to a specific message (deletes that message and all subsequent)", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "The bot ID" }, + leadId: { type: "string", description: "The test session lead ID" }, + messageId: { type: "string", description: "The message ID to rollback to (this and subsequent are deleted)" }, + }, + required: ["botId", "leadId", "messageId"], + }, + }, +]; + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + switch (name) { + case "create_test_session": + return ok( + await client.post(`/bot/${args.botId}/testSession`) + ); + + case "list_test_sessions": + return ok( + await client.get(`/bot/${args.botId}/testSession`, { + offset: args.offset, + maxCount: args.maxCount, + }) + ); + + case "delete_test_session": + return ok( + await client.delete(`/bot/${args.botId}/testSession/${args.leadId}`) + ); + + case "update_test_session": { + const body: UpdateSessionInput = { + mimicSourceId: args.mimicSourceId as string | undefined, + }; + return ok( + await client.put( + `/bot/${args.botId}/testSession/${args.leadId}`, + body + ) + ); + } + + case "send_test_message": { + const body: TestSessionMessageInput = { + leadId: args.leadId as string, + message: args.message as string, + }; + return ok( + await client.post(`/bot/${args.botId}/testSession/message`, body) + ); + } + + case "force_test_step": + return ok( + await client.post( + `/bot/${args.botId}/testSession/${args.leadId}/force-step` + ) + ); + + case "rollback_test_session": { + const body: BotTestingRollbackInput = { + messageId: args.messageId as string, + }; + return ok( + await client.post( + `/bot/${args.botId}/testSession/${args.leadId}/rollback`, + body + ) + ); + } + + default: + return err(`Unknown tool: ${name}`); + } + } catch (error) { + return err(error); + } +} diff --git a/src/tools/configuration.ts b/src/tools/configuration.ts new file mode 100644 index 0000000..860705f --- /dev/null +++ b/src/tools/configuration.ts @@ -0,0 +1,539 @@ +import { CloseBotClient, ok, err } from "../client.js"; +import type { + ToolDefinition, ToolResult, PersonaDto, CreatePersonaInput, UpdatePersonaInput, + SmartFAQDto, CreateSmartFAQRequest, AnswerMultipleFAQsRequest, AnsweredFAQFollowUpRequest, + ListHierarchyResult, AddHierarchyInput, HierarchyInput, + NotificationDto, NotificationUpdateDto, NotificationForwardingDto, + ApiKeyDTO, CreateApiKeyInput, CreateApiKeyOutput, + LiveDemoDto, LiveDemoCreateDto, + BotVariableDto, BotVariableUpdateInput, +} from "../types.js"; + +export const tools: ToolDefinition[] = [ + // --- Personas --- + { + name: "list_personas", + description: "List all personas in the agency", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "get_persona", + description: "Get a persona by ID", + inputSchema: { + type: "object", + properties: { id: { type: "string", description: "Persona ID" } }, + required: ["id"], + }, + }, + { + name: "create_persona", + description: "Create a new persona with voice style, response settings, and AI preferences", + inputSchema: { + type: "object", + properties: { + personaName: { type: "string", description: "Persona name" }, + description: { type: "string", description: "Description" }, + voiceStyles: { type: "string", description: "Voice styles" }, + howToRespond: { type: "string", description: "How to respond instructions" }, + typoPercent: { type: "number", description: "Typo percentage (0-100)" }, + breakupLargeMessagePercent: { type: "number", description: "Break up large message percent (0-100)" }, + responseTime: { type: "string", description: "Response time setting" }, + responseDelay: { type: "number", description: "Response delay in ms" }, + aiProviderPreferences: { type: "array", items: { type: "string" }, description: "AI provider preferences" }, + color: { type: "string", description: "Persona color" }, + }, + required: ["personaName"], + }, + }, + { + name: "update_persona", + description: "Update a persona. Only non-null fields are updated.", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "Persona ID" }, + personaName: { type: "string" }, + description: { type: "string" }, + voiceStyles: { type: "string" }, + howToRespond: { type: "string" }, + typoPercent: { type: "number" }, + breakupLargeMessagePercent: { type: "number" }, + responseTime: { type: "string" }, + responseDelay: { type: "number" }, + aiProviderPreferences: { type: "array", items: { type: "string" } }, + folderId: { type: "string" }, + favorited: { type: "boolean" }, + trash: { type: "boolean" }, + default: { type: "boolean" }, + color: { type: "string" }, + }, + required: ["id"], + }, + }, + { + name: "delete_persona", + description: "Delete a persona", + inputSchema: { + type: "object", + properties: { id: { type: "string", description: "Persona ID" } }, + required: ["id"], + }, + }, + // --- FAQs --- + { + name: "list_faqs", + description: "List smart FAQs, optionally filtered by source, state, and answered status", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "Source ID" }, + state: { type: "string", description: "State: compressed or uncompressed" }, + answered: { type: "string", description: "Filter: answered or unanswered" }, + }, + }, + }, + { + name: "create_faq", + description: "Create a new FAQ for a source", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "Source ID" }, + question: { type: "string", description: "FAQ question" }, + answer: { type: "string", description: "FAQ answer" }, + }, + required: ["sourceId", "question"], + }, + }, + { + name: "delete_faq", + description: "Delete an FAQ", + inputSchema: { + type: "object", + properties: { id: { type: "string", description: "FAQ ID" } }, + required: ["id"], + }, + }, + { + name: "answer_faqs", + description: "Answer multiple FAQs at once", + inputSchema: { + type: "object", + properties: { + faqs: { + type: "array", + items: { + type: "object", + properties: { + id: { type: "string", description: "FAQ ID" }, + answer: { type: "string", description: "Answer text" }, + }, + }, + description: "Array of FAQ answers", + }, + }, + required: ["faqs"], + }, + }, + { + name: "followup_answered_faqs", + description: "Follow up with leads who asked a now-answered FAQ", + inputSchema: { + type: "object", + properties: { + faqId: { type: "string", description: "The answered FAQ ID" }, + leadIds: { type: "array", items: { type: "string" }, description: "Lead IDs to follow up with" }, + }, + required: ["faqId", "leadIds"], + }, + }, + // --- Folders --- + { + name: "list_folders", + description: "List all folders (hierarchy) in the agency", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "get_folder", + description: "Get a folder by ID", + inputSchema: { + type: "object", + properties: { id: { type: "string", description: "Folder ID" } }, + required: ["id"], + }, + }, + { + name: "create_folder", + description: "Create a new folder", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "Folder name" }, + parentId: { type: "string", description: "Parent folder ID" }, + }, + required: ["name"], + }, + }, + { + name: "update_folder", + description: "Rename a folder", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "Folder ID" }, + name: { type: "string", description: "New name" }, + }, + required: ["id", "name"], + }, + }, + { + name: "delete_folder", + description: "Delete a folder", + inputSchema: { + type: "object", + properties: { id: { type: "string", description: "Folder ID" } }, + required: ["id"], + }, + }, + // --- Notifications --- + { + name: "list_notifications", + description: "List all notifications", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "update_notification", + description: "Update a notification (mark as viewed)", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "Notification ID" }, + viewed: { type: "boolean", description: "Mark as viewed" }, + }, + required: ["id"], + }, + }, + { + name: "delete_notification", + description: "Delete a notification", + inputSchema: { + type: "object", + properties: { id: { type: "string", description: "Notification ID" } }, + required: ["id"], + }, + }, + { + name: "get_notification_forwarding", + description: "Get notification forwarding settings", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "update_notification_forwarding", + description: "Update notification forwarding settings", + inputSchema: { + type: "object", + properties: { + enabled: { type: "boolean", description: "Enable forwarding" }, + channelsEnabled: { type: "array", items: { type: "string" }, description: "Channels to forward" }, + webhookEndpoint: { type: "string", description: "Webhook endpoint" }, + }, + }, + }, + // --- API Keys --- + { + name: "list_api_keys", + description: "List all API keys", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "create_api_key", + description: "Create a new API key", + inputSchema: { + type: "object", + properties: { name: { type: "string", description: "Key name" } }, + required: ["name"], + }, + }, + { + name: "delete_api_key", + description: "Delete an API key", + inputSchema: { + type: "object", + properties: { keyId: { type: "string", description: "Key ID" } }, + required: ["keyId"], + }, + }, + // --- Webhook --- + { + name: "send_webhook_event", + description: "Send a webhook event to a WebHook source", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "The WebHook source ID" }, + body: { type: "object", description: "The webhook event body (any JSON)" }, + }, + required: ["sourceId", "body"], + }, + }, + // --- Live Demo --- + { + name: "list_live_demos", + description: "List live demos for a bot", + inputSchema: { + type: "object", + properties: { botId: { type: "string", description: "Bot ID" } }, + required: ["botId"], + }, + }, + { + name: "create_live_demo", + description: "Create a live demo for a bot", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "Bot ID" }, + name: { type: "string", description: "Demo name" }, + mimicSourceId: { type: "string", description: "Source ID to mimic" }, + active: { type: "boolean", description: "Whether demo is active" }, + }, + required: ["botId", "name"], + }, + }, + { + name: "update_live_demo", + description: "Update a live demo", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "Bot ID" }, + key: { type: "string", description: "Demo key" }, + name: { type: "string", description: "Demo name" }, + mimicSourceId: { type: "string", description: "Source ID to mimic" }, + active: { type: "boolean", description: "Whether active" }, + }, + required: ["botId", "key"], + }, + }, + { + name: "delete_live_demo", + description: "Delete a live demo", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "Bot ID" }, + key: { type: "string", description: "Demo key" }, + }, + required: ["botId", "key"], + }, + }, + // --- Bot Source Variables --- + { + name: "get_source_variables", + description: "Get source variables for a bot-source pair", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "Bot ID" }, + sourceId: { type: "string", description: "Source ID" }, + }, + required: ["botId", "sourceId"], + }, + }, + { + name: "update_source_variables", + description: "Update source variables for a bot-source pair", + inputSchema: { + type: "object", + properties: { + botId: { type: "string", description: "Bot ID" }, + sourceId: { type: "string", description: "Source ID" }, + variables: { + type: "array", + items: { + type: "object", + properties: { + id: { type: "string", description: "Variable ID" }, + value: { type: "string", description: "Variable value" }, + }, + }, + description: "Variables to update", + }, + }, + required: ["botId", "sourceId", "variables"], + }, + }, +]; + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + switch (name) { + // Personas + case "list_personas": + return ok(await client.get("/persona")); + case "get_persona": + return ok(await client.get(`/persona/${args.id}`)); + case "create_persona": { + const body: CreatePersonaInput = { + personaName: args.personaName as string, + description: args.description as string | undefined, + voiceStyles: args.voiceStyles as string | undefined, + howToRespond: args.howToRespond as string | undefined, + typoPercent: (args.typoPercent as number) || 0, + breakupLargeMessagePercent: (args.breakupLargeMessagePercent as number) || 0, + responseTime: args.responseTime as string | undefined, + responseDelay: (args.responseDelay as number) || 0, + aiProviderPreferences: args.aiProviderPreferences as string[] | undefined, + color: args.color as string | undefined, + }; + return ok(await client.post("/persona", body)); + } + case "update_persona": { + const { id, ...rest } = args; + const body: UpdatePersonaInput = {}; + for (const [k, v] of Object.entries(rest)) { + if (v !== undefined) (body as Record)[k] = v; + } + return ok(await client.put(`/persona/${id}`, body)); + } + case "delete_persona": + return ok(await client.delete(`/persona/${args.id}`)); + + // FAQs + case "list_faqs": + return ok( + await client.get("/smart-faq", { + sourceId: args.sourceId, + state: args.state, + answered: args.answered, + }) + ); + case "create_faq": { + const body: CreateSmartFAQRequest = { + sourceId: args.sourceId as string, + question: args.question as string, + answer: args.answer as string | undefined, + }; + return ok(await client.post("/smart-faq", body)); + } + case "delete_faq": + return ok(await client.delete(`/smart-faq/${args.id}`)); + case "answer_faqs": { + const body: AnswerMultipleFAQsRequest = { + faQs: args.faqs as Array<{ id?: string; answer?: string }>, + }; + return ok(await client.post("/smart-faq/answer", body)); + } + case "followup_answered_faqs": { + const body: AnsweredFAQFollowUpRequest = { + faqId: args.faqId as string, + leadIds: args.leadIds as string[], + }; + return ok(await client.post("/smart-faq/answered-followup", body)); + } + + // Folders + case "list_folders": + return ok(await client.get("/hierarchy")); + case "get_folder": + return ok(await client.get(`/hierarchy/${args.id}`)); + case "create_folder": { + const body: AddHierarchyInput = { + name: args.name as string, + parentId: args.parentId as string | undefined, + }; + return ok(await client.post("/hierarchy", body)); + } + case "update_folder": { + const body: HierarchyInput = { name: args.name as string }; + return ok(await client.put(`/hierarchy/${args.id}`, body)); + } + case "delete_folder": + return ok(await client.delete(`/hierarchy/${args.id}`)); + + // Notifications + case "list_notifications": + return ok(await client.get("/notifications")); + case "update_notification": { + const body: NotificationUpdateDto = { viewed: args.viewed as boolean }; + return ok(await client.put(`/notifications/${args.id}`, body)); + } + case "delete_notification": + return ok(await client.delete(`/notifications/${args.id}`)); + case "get_notification_forwarding": + return ok(await client.get("/notifications/forwarding")); + case "update_notification_forwarding": { + const body: NotificationForwardingDto = { + enabled: args.enabled as boolean, + channelsEnabled: args.channelsEnabled as string[] | undefined, + webhookEndpoint: args.webhookEndpoint as string | undefined, + }; + return ok(await client.put("/notifications/forwarding", body)); + } + + // API Keys + case "list_api_keys": + return ok(await client.get("/account/apiKey")); + case "create_api_key": { + const body: CreateApiKeyInput = { name: args.name as string }; + return ok(await client.post("/account/apiKey", body)); + } + case "delete_api_key": + return ok(await client.delete(`/account/apiKey/${args.keyId}`)); + + // Webhook + case "send_webhook_event": + return ok( + await client.post(`/webhook/event/${args.sourceId}`, args.body) + ); + + // Live Demo + case "list_live_demos": + return ok(await client.get(`/bot-live-demo/${args.botId}`)); + case "create_live_demo": { + const body: LiveDemoCreateDto = { + name: args.name as string, + mimicSourceId: args.mimicSourceId as string | undefined, + active: (args.active as boolean) ?? true, + }; + return ok(await client.post(`/bot-live-demo/${args.botId}`, body)); + } + case "update_live_demo": { + const body: LiveDemoCreateDto = { + name: args.name as string | undefined, + mimicSourceId: args.mimicSourceId as string | undefined, + active: args.active as boolean | undefined, + }; + return ok( + await client.put(`/bot-live-demo/${args.botId}/${args.key}`, body) + ); + } + case "delete_live_demo": + return ok(await client.delete(`/bot-live-demo/${args.botId}/${args.key}`)); + + // Bot Source Variables + case "get_source_variables": + return ok( + await client.get( + `/botVariables/${args.botId}/${args.sourceId}` + ) + ); + case "update_source_variables": + return ok( + await client.post( + `/botVariables/${args.botId}/${args.sourceId}`, + args.variables as BotVariableUpdateInput[] + ) + ); + + default: + return err(`Unknown tool: ${name}`); + } + } catch (error) { + return err(error); + } +} diff --git a/src/tools/lead-management.ts b/src/tools/lead-management.ts new file mode 100644 index 0000000..ad1447e --- /dev/null +++ b/src/tools/lead-management.ts @@ -0,0 +1,157 @@ +import { CloseBotClient, ok, err } from "../client.js"; +import type { ToolDefinition, ToolResult, LeadDto, LeadDtoPaginated, SearchQueryInput, LeadUpdateDto, InstanceUpdateDto } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "list_leads", + description: "List leads with pagination, optionally filtered by source or lead ID", + inputSchema: { + type: "object", + properties: { + page: { type: "number", description: "Page number (0-indexed)" }, + pageSize: { type: "number", description: "Page size (default 20)" }, + sourceId: { type: "string", description: "Filter by source ID" }, + leadId: { type: "string", description: "Filter by lead ID" }, + }, + }, + }, + { + name: "search_leads", + description: "Search leads with advanced filters (source, channel, bot, persona, etc.)", + inputSchema: { + type: "object", + properties: { + search: { type: "string", description: "Search query string" }, + offset: { type: "number", description: "Result offset" }, + count: { type: "number", description: "Number of results" }, + sourceIds: { type: "array", items: { type: "string" }, description: "Filter by source IDs" }, + channels: { type: "array", items: { type: "string" }, description: "Filter by channels" }, + botIds: { type: "array", items: { type: "string" }, description: "Filter by bot IDs" }, + personaIds: { type: "array", items: { type: "string" }, description: "Filter by persona IDs" }, + minimumResponses: { type: "number", description: "Minimum number of responses" }, + lastMessageDirection: { type: "string", description: "Filter by last message direction (inbound/outbound)" }, + followUpScheduled: { type: "boolean", description: "Filter by follow-up scheduled status" }, + }, + }, + }, + { + name: "get_lead", + description: "Get detailed lead information by ID", + inputSchema: { + type: "object", + properties: { + leadId: { type: "string", description: "The lead ID" }, + }, + required: ["leadId"], + }, + }, + { + name: "update_lead_fields", + description: "Update custom fields on a lead", + inputSchema: { + type: "object", + properties: { + leadId: { type: "string", description: "The lead ID" }, + fields: { + type: "array", + description: "Array of field updates", + items: { + type: "object", + properties: { + field: { type: "string", description: "Field name/key" }, + value: { type: "string", description: "Field value" }, + }, + }, + }, + }, + required: ["leadId", "fields"], + }, + }, + { + name: "delete_lead", + description: "Delete a lead by ID", + inputSchema: { + type: "object", + properties: { + leadId: { type: "string", description: "The lead ID" }, + }, + required: ["leadId"], + }, + }, + { + name: "update_lead_instance", + description: "Update a lead's bot instance (e.g., set follow-up time)", + inputSchema: { + type: "object", + properties: { + leadId: { type: "string", description: "The lead ID" }, + botId: { type: "string", description: "The bot ID" }, + followUpTime: { type: "string", description: "Follow-up time (ISO 8601 datetime)" }, + }, + required: ["leadId", "botId"], + }, + }, +]; + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + switch (name) { + case "list_leads": + return ok( + await client.get("/lead", { + page: args.page, + pageSize: args.pageSize, + sourceId: args.sourceId, + leadId: args.leadId, + }) + ); + + case "search_leads": { + const body: SearchQueryInput = { + search: args.search as string | undefined, + offset: args.offset as number | undefined, + count: args.count as number | undefined, + sourceIds: args.sourceIds as string[] | undefined, + channels: args.channels as string[] | undefined, + botIds: args.botIds as string[] | undefined, + personaIds: args.personaIds as string[] | undefined, + minimumResponses: args.minimumResponses as number | undefined, + lastMessageDirection: args.lastMessageDirection as string | undefined, + followUpScheduled: args.followUpScheduled as boolean | undefined, + }; + return ok(await client.post("/lead/search", body)); + } + + case "get_lead": + return ok(await client.get(`/lead/${args.leadId}`)); + + case "update_lead_fields": { + const body: LeadUpdateDto = { + fields: args.fields as Array<{ field?: string; value?: string }>, + }; + return ok(await client.put(`/lead/${args.leadId}`, body)); + } + + case "delete_lead": + return ok(await client.delete(`/lead/${args.leadId}`)); + + case "update_lead_instance": { + const body: InstanceUpdateDto = {}; + if (args.followUpTime !== undefined) + body.followUpTime = args.followUpTime as string; + return ok( + await client.put(`/lead/${args.leadId}/instance/${args.botId}`, body) + ); + } + + default: + return err(`Unknown tool: ${name}`); + } + } catch (error) { + return err(error); + } +} diff --git a/src/tools/library.ts b/src/tools/library.ts new file mode 100644 index 0000000..053766e --- /dev/null +++ b/src/tools/library.ts @@ -0,0 +1,232 @@ +import { CloseBotClient, ok, err } from "../client.js"; +import type { ToolDefinition, ToolResult, FileDto, WebscrapePageDto, WebscrapePageUpdateDto, UploadWebscrapeToSourceInput } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "list_files", + description: "List all files in the knowledge base library", + inputSchema: { type: "object", properties: {} }, + }, + { + name: "get_file", + description: "Get metadata for a specific file", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "The file ID" }, + }, + required: ["fileId"], + }, + }, + { + name: "delete_file", + description: "Delete a file from the library", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "The file ID" }, + }, + required: ["fileId"], + }, + }, + { + name: "view_file_content", + description: "View the content of a file", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "The file ID" }, + }, + required: ["fileId"], + }, + }, + { + name: "get_scrape_pages", + description: "Get the web scrape pages for a file", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "The file ID" }, + }, + required: ["fileId"], + }, + }, + { + name: "update_scrape_pages", + description: "Update the web scrape pages for a file (enable/disable specific URLs)", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "The file ID" }, + pages: { + type: "array", + description: "Array of page configs", + items: { + type: "object", + properties: { + url: { type: "string", description: "Page URL" }, + enabled: { type: "boolean", description: "Whether the page is enabled" }, + }, + }, + }, + }, + required: ["fileId", "pages"], + }, + }, + { + name: "create_web_scrape", + description: "Create a new web scrape file from a URL", + inputSchema: { + type: "object", + properties: { + scrapeUrl: { type: "string", description: "URL to scrape" }, + maxBreadth: { type: "number", description: "Maximum breadth of the scrape" }, + maxDepth: { type: "number", description: "Maximum depth of the scrape" }, + }, + required: ["scrapeUrl"], + }, + }, + { + name: "attach_file_to_source", + description: "Attach a library file to a source for knowledge base use", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "The file ID" }, + sourceId: { type: "string", description: "The source ID" }, + }, + required: ["fileId", "sourceId"], + }, + }, + { + name: "detach_file_from_source", + description: "Detach a library file from a source", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "The file ID" }, + sourceId: { type: "string", description: "The source ID" }, + }, + required: ["fileId", "sourceId"], + }, + }, + { + name: "upload_file", + description: "Upload a file to the library. Provide base64-encoded file content.", + inputSchema: { + type: "object", + properties: { + fileName: { type: "string", description: "File name with extension" }, + fileContent: { type: "string", description: "Base64-encoded file content" }, + mimeType: { type: "string", description: "MIME type of the file (e.g., text/plain, application/pdf)" }, + }, + required: ["fileName", "fileContent"], + }, + }, + { + name: "replace_file_content", + description: "Replace the content of an existing file. Provide base64-encoded new content.", + inputSchema: { + type: "object", + properties: { + fileId: { type: "string", description: "The file ID" }, + fileName: { type: "string", description: "File name with extension" }, + fileContent: { type: "string", description: "Base64-encoded new file content" }, + mimeType: { type: "string", description: "MIME type of the file" }, + }, + required: ["fileId", "fileContent"], + }, + }, +]; + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + switch (name) { + case "list_files": + return ok(await client.get("/library/files")); + + case "get_file": + return ok(await client.get(`/library/files/${args.fileId}`)); + + case "delete_file": + return ok(await client.delete(`/library/files/${args.fileId}`)); + + case "view_file_content": + return ok(await client.get(`/library/files/${args.fileId}/view`)); + + case "get_scrape_pages": + return ok( + await client.get( + `/library/files/${args.fileId}/scrape-pages` + ) + ); + + case "update_scrape_pages": { + const body: WebscrapePageUpdateDto = { + pages: args.pages as WebscrapePageDto[], + }; + return ok( + await client.put(`/library/files/${args.fileId}/scrape-pages`, body) + ); + } + + case "create_web_scrape": { + const body: UploadWebscrapeToSourceInput = { + scrapeUrl: args.scrapeUrl as string, + maxBreadth: (args.maxBreadth as number) || 10, + maxDepth: (args.maxDepth as number) || 2, + }; + return ok(await client.post("/library/webscrape", body)); + } + + case "attach_file_to_source": + return ok( + await client.post( + `/library/files/${args.fileId}/source/${args.sourceId}` + ) + ); + + case "detach_file_from_source": + return ok( + await client.delete( + `/library/files/${args.fileId}/source/${args.sourceId}` + ) + ); + + case "upload_file": { + const buffer = Buffer.from(args.fileContent as string, "base64"); + const blob = new Blob([buffer], { + type: (args.mimeType as string) || "application/octet-stream", + }); + const formData = new FormData(); + formData.append("file", blob, args.fileName as string); + return ok(await client.postFormData("/library/files", formData)); + } + + case "replace_file_content": { + const buffer = Buffer.from(args.fileContent as string, "base64"); + const blob = new Blob([buffer], { + type: (args.mimeType as string) || "application/octet-stream", + }); + const formData = new FormData(); + formData.append( + "newFile", + blob, + (args.fileName as string) || "file" + ); + return ok( + await client.putFormData(`/library/files/${args.fileId}`, formData) + ); + } + + default: + return err(`Unknown tool: ${name}`); + } + } catch (error) { + return err(error); + } +} diff --git a/src/tools/source-management.ts b/src/tools/source-management.ts new file mode 100644 index 0000000..f983399 --- /dev/null +++ b/src/tools/source-management.ts @@ -0,0 +1,213 @@ +import { CloseBotClient, ok, err } from "../client.js"; +import type { ToolDefinition, ToolResult, SourceDto, SourceDtoPaginated, AddSourceInput, UpdateSourceInput, SourceCalendarDto, SourceChannelDto, SourceFieldCollectionDto, SourceTagDto } from "../types.js"; + +export const tools: ToolDefinition[] = [ + { + name: "list_sources", + description: "List sources in the agency with pagination and filtering", + inputSchema: { + type: "object", + properties: { + page: { type: "number", description: "Page number (0-indexed)" }, + pageSize: { type: "number", description: "Page size (max 100, default 20)" }, + query: { type: "string", description: "Search by source name" }, + category: { type: "string", description: "Filter by category (GHL, HubSpot, WebHook, etc.)" }, + order: { type: "string", description: "Order results. +/- prefix for asc/desc (default: +id)" }, + forceTokenRefresh: { type: "boolean", description: "Refresh access tokens for returned sources" }, + }, + }, + }, + { + name: "get_source", + description: "Get detailed information for a single source", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The source ID" }, + }, + required: ["id"], + }, + }, + { + name: "add_source", + description: "Add a new source to the agency", + inputSchema: { + type: "object", + properties: { + name: { type: "string", description: "Source name" }, + category: { type: "string", description: "Source category (GHL, WebHook, etc.)" }, + key: { type: "string", description: "Source key/identifier" }, + accessToken: { type: "string", description: "Access token for the source" }, + refreshToken: { type: "string", description: "Refresh token" }, + expiresIn: { type: "number", description: "Token expiry in seconds" }, + autoShutoff: { type: "boolean", description: "Enable auto shutoff" }, + gracefulGoodbye: { type: "boolean", description: "Enable graceful goodbye" }, + summarizeAttachments: { type: "boolean", description: "Enable attachment summarization" }, + webhookCallback: { type: "string", description: "Webhook callback URL" }, + }, + required: ["name"], + }, + }, + { + name: "update_source", + description: "Update source fields. Only non-null fields are updated.", + inputSchema: { + type: "object", + properties: { + sourceId: { type: "string", description: "The source ID" }, + name: { type: "string", description: "New name" }, + autoShutoff: { type: "boolean", description: "Auto shutoff setting" }, + gracefulGoodbye: { type: "boolean", description: "Graceful goodbye setting" }, + isAvailabilityContactTimezone: { type: "boolean", description: "Use contact timezone for availability" }, + summarizeAttachments: { type: "boolean", description: "Summarize attachments" }, + respondToReactions: { type: "boolean", description: "Respond to reactions" }, + markConversationsAsUnread: { type: "boolean", description: "Mark conversations as unread" }, + webhookCallback: { type: "string", description: "Webhook callback URL" }, + }, + required: ["sourceId"], + }, + }, + { + name: "delete_source", + description: "Delete a source and all associated data. Also attempts to uninstall from external CRM.", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The source ID" }, + }, + required: ["id"], + }, + }, + { + name: "list_source_calendars", + description: "List all calendars attached to a source (manual + CRM calendars)", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The source ID" }, + }, + required: ["id"], + }, + }, + { + name: "list_source_channels", + description: "List all channels available to a source", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The source ID" }, + }, + required: ["id"], + }, + }, + { + name: "list_source_fields", + description: "List available fields for a source (contact, location, custom values)", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The source ID" }, + }, + required: ["id"], + }, + }, + { + name: "list_source_tags", + description: "List all tags for a source", + inputSchema: { + type: "object", + properties: { + id: { type: "string", description: "The source ID" }, + }, + required: ["id"], + }, + }, +]; + +export async function handler( + client: CloseBotClient, + name: string, + args: Record +): Promise { + try { + switch (name) { + case "list_sources": + return ok( + await client.get("/agency/source", { + page: args.page, + pageSize: args.pageSize, + query: args.query, + category: args.category, + order: args.order, + forceTokenRefresh: args.forceTokenRefresh, + }) + ); + + case "get_source": + return ok(await client.get(`/agency/source/${args.id}`)); + + case "add_source": { + const body: AddSourceInput = { + name: args.name as string, + category: args.category as string | undefined, + key: args.key as string | undefined, + accessToken: args.accessToken as string | undefined, + refreshToken: args.refreshToken as string | undefined, + expiresIn: args.expiresIn as number | undefined, + autoShutoff: args.autoShutoff as boolean | undefined, + gracefulGoodbye: args.gracefulGoodbye as boolean | undefined, + summarizeAttachments: args.summarizeAttachments as boolean | undefined, + webhookCallback: args.webhookCallback as string | undefined, + }; + return ok(await client.post("/agency/source", body)); + } + + case "update_source": { + const { sourceId, ...rest } = args; + const body: UpdateSourceInput = {}; + if (rest.name !== undefined) body.name = rest.name as string; + if (rest.autoShutoff !== undefined) body.autoShutoff = rest.autoShutoff as boolean; + if (rest.gracefulGoodbye !== undefined) body.gracefulGoodbye = rest.gracefulGoodbye as boolean; + if (rest.isAvailabilityContactTimezone !== undefined) + body.isAvailabilityContactTimezone = rest.isAvailabilityContactTimezone as boolean; + if (rest.summarizeAttachments !== undefined) + body.summarizeAttachments = rest.summarizeAttachments as boolean; + if (rest.respondToReactions !== undefined) + body.respondToReactions = rest.respondToReactions as boolean; + if (rest.markConversationsAsUnread !== undefined) + body.markConversationsAsUnread = rest.markConversationsAsUnread as boolean; + if (rest.webhookCallback !== undefined) + body.webhookCallback = rest.webhookCallback as string; + return ok(await client.put(`/agency/source/${sourceId}`, body)); + } + + case "delete_source": + return ok(await client.delete(`/agency/source/${args.id}`)); + + case "list_source_calendars": + return ok( + await client.get(`/agency/source/${args.id}/calendars`) + ); + + case "list_source_channels": + return ok( + await client.get(`/agency/source/${args.id}/channels`) + ); + + case "list_source_fields": + return ok( + await client.get(`/agency/source/${args.id}/fields`) + ); + + case "list_source_tags": + return ok( + await client.get(`/agency/source/${args.id}/tags`) + ); + + default: + return err(`Unknown tool: ${name}`); + } + } catch (error) { + return err(error); + } +} diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..5dd497c --- /dev/null +++ b/src/types.ts @@ -0,0 +1,940 @@ +// ============================================================================ +// CloseBot API Types — Generated from OpenAPI 3.0.1 Swagger Spec +// ============================================================================ + +// ---------- Common / Shared ---------- + +export interface ProblemDetails { + type?: string | null; + title?: string | null; + status?: number | null; + detail?: string | null; + instance?: string | null; + [key: string]: unknown; +} + +// ---------- Account ---------- + +export interface CreateApiKeyInput { + name?: string | null; +} + +export interface CreateApiKeyOutput { + id?: string | null; + key?: string | null; + name?: string | null; + createdAt?: string; + expiresAt?: string; +} + +export interface ApiKeyDTO { + id?: string | null; + name?: string | null; + createdAt?: string; + expiresAt?: string; +} + +// ---------- Agency ---------- + +export interface AgencyDto { + id?: string | null; + name?: string | null; + members?: AgencyMemberDto[] | null; +} + +export interface AgencyMemberDto { + accountId?: string | null; + role?: string | null; + authId?: string | null; + name?: string | null; + email?: string | null; + status?: string | null; +} + +export interface InviteUserInput { + invites?: Invite[] | null; +} + +export interface Invite { + email?: string | null; + role?: string | null; + sourceIds?: string[] | null; +} + +// ---------- Billing ---------- + +export interface BalanceDto { + balance?: number; + currency?: string | null; +} + +export interface BillingOptionsDto { + overBillingEnabled?: boolean; + usageBillingEnabled?: boolean; + autoRefillEnabled?: boolean; + topUpAmount?: number; + refillThreshold?: number; + currency?: string | null; +} + +export interface UpdateBillingConfigInput { + overBillingEnabled?: boolean | null; + autoRefillEnabled?: boolean | null; + topUpAmount?: number | null; + refillThreshold?: number | null; +} + +export interface CreateRefillDto { + amount?: number; + currency?: string | null; +} + +export interface ReBillingDto { + reBillingConfigured?: boolean; + responseUnitCost?: string | null; + storageUnitCost?: string | null; + userUnitCost?: string | null; + tokenMultiplier?: string | null; +} + +export interface ReBillingUpdateInput { + enabled?: boolean | null; + responseCost?: string | null; + storageCost?: string | null; + userCost?: string | null; + tokenMultiplier?: string | null; +} + +export interface TransactionDto { + id?: number; + status?: string | null; + description?: string | null; + amount?: number; + currency?: string | null; + createdAt?: string; + receiptUrl?: string | null; + usage?: BilledUsageDto; +} + +export interface AddSourceTransactionDto { + amount?: string | null; + description?: string | null; +} + +export interface BilledUsageDto { + startTime?: string; + endTime?: string; + responses?: number; + libraryBytes?: number; + users?: number; + responseCost?: number; + libraryCost?: number; + userCost?: number; +} + +// ---------- Bot ---------- + +export interface BotDto { + id?: string | null; + name?: string | null; + modifiedAt?: string | null; + modifiedBy?: string | null; + versions?: BotVersionDto[] | null; + sources?: BotSourceDto[] | null; + personaIds?: string[] | null; + favorited?: boolean; + locked?: boolean; + reschedulingEnabled?: boolean; + category?: string | null; + folderId?: string | null; + followUpActive?: boolean; + followUpSequences?: FollowUpSequenceDto[] | null; + smartFollowUp?: boolean; + followUpRepeat?: boolean; + followUpVarianceMinutes?: number; + followUpExtraPrompt?: string | null; + tools?: BotToolDto[] | null; +} + +export interface BotVersionDto { + version?: string | null; + name?: string | null; + published?: boolean; + modifiedAt?: string; + modifiedBy?: string | null; +} + +export interface BotSourceDto { + id?: string | null; + category?: string | null; + key?: string | null; + name?: string | null; + tags?: ContactTag[] | null; + channelList?: string[] | null; + personaNameOverride?: string | null; + enabled?: boolean; +} + +export interface ContactTag { + name?: string | null; + approveDeny?: boolean; + id?: string | null; +} + +export interface FollowUpSequenceDto { + order?: number; + duration?: number; + unit?: string | null; +} + +export interface BotToolDto { + id?: string | null; + type?: string | null; + enabled?: boolean; + options?: Record; +} + +export interface CreateBotInput { + name?: string | null; + templateId?: string | null; + importKdl?: string | null; + folderId?: string | null; + category?: string | null; +} + +export interface AiCreateBotInput { + name?: string | null; + description?: string | null; + category?: string | null; + folderId?: string | null; +} + +export interface UpdateBotInput { + favorite?: boolean | null; + trash?: boolean | null; + locked?: boolean | null; + rescheduling?: boolean | null; + name?: string | null; + folderId?: string | null; + category?: string | null; + followUpActive?: boolean | null; + followUpSequences?: FollowUpSequenceDto[] | null; + smartFollowUp?: boolean | null; + followUpRepeat?: boolean | null; + followUpVarianceMinutes?: number | null; + followUpExtraPrompt?: string | null; +} + +export interface UpdateBotErrorResponse { + message?: string | null; +} + +export interface AttachSourceInput { + tags?: ContactTag[] | null; + channels?: string[] | null; + personaNameOverride?: string | null; + enabled?: boolean | null; +} + +export interface ExportBotResponse { + id?: string | null; + kdl?: string | null; + version?: string | null; +} + +export interface SaveBotInput { + botSteps?: unknown; +} + +export interface SaveBotResponse { + version?: string | null; + invalidPaths?: string[] | null; + message?: string | null; +} + +export interface PublishBotResponse { + version?: string | null; + message?: unknown; +} + +export interface UpdateVersionInput { + name?: string | null; + importKdl?: string | null; +} + +export interface ToolInputDto { + type?: string | null; + enabled?: boolean; + options?: unknown; +} + +// ---------- Bot Metrics ---------- + +export interface AgencyDashboardSummaryResponse { + currentMonthMessageCount?: number; + lastMonthMessageCount?: number; + totalStorage?: number; + currentMonthSuccessfulBookings?: number; + lastMonthSuccessfulBookings?: number; + currentMonthActiveSources?: number; + lastMonthActiveSources?: number; + currentUsers?: number; + currentMonthContacts?: number; + lastMonthContacts?: number; +} + +export interface BotMetricAction { + timestamp?: string; + actionId?: string | null; + leadId?: string | null; + sourceId?: string | null; + botId?: string | null; + nodeId?: number; + frontendNodeId?: string | null; +} + +export interface BotMetricMessage { + messageId?: string | null; + sourceId?: string | null; + leadId?: string | null; + botId?: string | null; + personaId?: string | null; + channel?: string | null; + fromBot?: boolean; + direction?: string | null; + message?: string | null; + attachments?: BotMetricAttachment[] | null; + timestamp?: string; + activities?: BotMetricActivity[] | null; +} + +export interface BotMetricAttachment { + url?: string | null; +} + +export interface BotMetricActivity { + activity?: string | null; + data?: string | null; + timeData?: string | null; +} + +export interface BotMetricLog { + timestamp?: string; + botId?: string | null; + messageId?: string | null; + sourceId?: string | null; + leadId?: string | null; + actionId?: string | null; + severity?: number | null; + message?: string | null; + prompt?: BotMetricPrompt[] | null; + response?: string | null; + promptTokens?: number | null; + completionTokens?: number | null; + provider?: string | null; + model?: string | null; + purpose?: string | null; + body?: string | null; +} + +export interface BotMetricPrompt { + kind?: string | null; + body?: string | null; +} + +export interface LeaderboardResponse { + agencyName?: string | null; + agencyId?: string | null; + count?: number; + value?: number; + rank?: number; + isCurrentAgency?: boolean; +} + +export interface MessageFeedbackRequest { + messageId?: string | null; + leadId?: string | null; + reasons?: string | null; + liked?: boolean | null; +} + +export interface MessageFeedbackResponse { + feedbackMessageIds?: string[] | null; +} + +export interface MessageLikesResponse { + likedMessageIds?: string[] | null; +} + +export interface MessageReason { + reason?: string | null; +} + +// ---------- Bot Source Variables ---------- + +export interface BotVariableDto { + id?: string | null; + name?: string | null; + value?: string | null; +} + +export interface BotVariableUpdateInput { + id?: string | null; + value?: string | null; +} + +// ---------- Bot Templates ---------- + +export interface BotTemplateDto { + id?: string | null; + name?: string | null; + description?: string | null; + level?: string | null; + industry?: string | null; + tags?: string[] | null; + videoUrl?: string | null; + previewUrl?: string | null; + tier?: string | null; +} + +// ---------- Bot Testing ---------- + +export interface TestSession { + leadId?: string | null; + sourceId?: string | null; +} + +export interface TestSessionMessageInput { + leadId?: string | null; + message?: string | null; +} + +export interface UpdateSessionInput { + mimicSourceId?: string | null; +} + +export interface UpdateSessionDto { + sessionId?: string | null; +} + +export interface BotTestingRollbackInput { + messageId?: string | null; +} + +export interface BotTestingRollbackOutput { + deletedMessageIds?: string[] | null; + deletedActionIds?: string[] | null; +} + +export interface ListLeadDto { + leads?: LeadDto[] | null; + total?: number; +} + +// ---------- Hierarchy (Folders) ---------- + +export interface AddHierarchyInput { + name?: string | null; + parentId?: string | null; +} + +export interface AddHierarchyOutput { + id?: string | null; +} + +export interface ListHierarchyResult { + id?: string | null; + name?: string | null; + bots?: string[] | null; + personas?: string[] | null; + children?: string[] | null; +} + +export interface HierarchyInput { + name?: string | null; +} + +// ---------- Lead ---------- + +export interface LeadDto { + id?: string | null; + name?: string | null; + contactId?: string | null; + lastMessageTime?: string | null; + lastMessage?: string | null; + lastMessageDirection?: string | null; + lastMessageBotId?: string | null; + mostRecentFailureReason?: string | null; + mimicSourceId?: string | null; + source?: LeadSourceDto; + tags?: string[] | null; + fields?: LeadFieldDto[] | null; + instances?: BotInstanceDto[] | null; +} + +export interface LeadDtoPaginated { + total?: number; + results?: LeadDto[] | null; + page?: number; + pageSize?: number; +} + +export interface LeadSourceDto { + id?: string | null; + name?: string | null; +} + +export interface LeadFieldDto { + field?: string | null; + value?: string | null; +} + +export interface LeadFieldUpdateDto { + field?: string | null; + value?: string | null; +} + +export interface LeadUpdateDto { + fields?: LeadFieldUpdateDto[] | null; +} + +export interface SearchQueryInput { + offset?: number | null; + count?: number | null; + search?: string | null; + sourceIds?: string[] | null; + channels?: string[] | null; + botIds?: string[] | null; + personaIds?: string[] | null; + minimumResponses?: number | null; + lastMessageDirection?: string | null; + followUpScheduled?: boolean | null; +} + +export interface InstanceUpdateDto { + followUpTime?: string | null; +} + +export interface BotInstanceDto { + botId?: string | null; + botVersion?: string | null; + followUpTimezoneKind?: string | null; + followUpTimezone?: string | null; + followUpTime?: string | null; + isSmartFollowUpTime?: boolean; +} + +// ---------- Library ---------- + +export interface FileDto { + fileId?: string | null; + fileName?: string | null; + lastModified?: string; + fileType?: string | null; + fileStatus?: string | null; + fileSize?: number; + estimatedFileSize?: number; + sources?: FileSourceDto[] | null; + accountId?: string | null; + uri?: string | null; +} + +export interface FileSourceDto { + id?: string | null; + name?: string | null; + category?: string | null; +} + +export interface UploadWebscrapeToSourceInput { + scrapeUrl?: string | null; + maxBreadth?: number; + maxDepth?: number; +} + +export interface WebscrapePageDto { + url?: string | null; + enabled?: boolean; +} + +export interface WebscrapePageUpdateDto { + pages?: WebscrapePageDto[] | null; +} + +// ---------- Live Demo ---------- + +export interface LiveDemoCreateDto { + name?: string | null; + mimicSourceId?: string | null; + active?: boolean; +} + +export interface LiveDemoDto { + name?: string | null; + key?: string | null; + organizationId?: string | null; + active?: boolean; + mimicSourceId?: string | null; + sourceVariables?: BotVariableDto[] | null; +} + +export interface LiveDemoSessionDto { + key?: string | null; + sessionLeadId?: string | null; +} + +export interface LiveDemoMessageInput { + message?: string | null; +} + +// ---------- Notification ---------- + +export interface NotificationDto { + id?: string | null; + kind?: string | null; + title?: string | null; + body?: string | null; + viewed?: boolean; + timestamp?: string; + metadata?: NotificationMetadata; +} + +export interface NotificationMetadata { + aiProviderId?: string | null; + sourceId?: string | null; + botId?: string | null; + rawAiError?: string | null; +} + +export interface NotificationUpdateDto { + viewed?: boolean; +} + +export interface NotificationForwardingDto { + enabled?: boolean; + channelsEnabled?: string[] | null; + webhookEndpoint?: string | null; +} + +// ---------- Persona ---------- + +export interface PersonaDto { + id?: string | null; + agencyId?: string | null; + personaName?: string | null; + description?: string | null; + color?: string | null; + imageUri?: string | null; + voiceStyles?: string | null; + howToRespond?: string | null; + typoPercent?: number; + breakupLargeMessagePercent?: number; + responseTime?: string | null; + responseDelay?: number; + modifiedAt?: string | null; + modifiedBy?: string | null; + aiProviderPreferences?: string[] | null; + folderId?: string | null; + botIds?: string[] | null; + bots?: BotPersonaDto[] | null; + favorited?: boolean; + default?: boolean; +} + +export interface BotPersonaDto { + id?: string | null; + name?: string | null; +} + +export interface CreatePersonaInput { + personaName?: string | null; + description?: string | null; + voiceStyles?: string | null; + howToRespond?: string | null; + typoPercent?: number; + breakupLargeMessagePercent?: number; + responseTime?: string | null; + responseDelay?: number; + aiProviderPreferences?: string[] | null; + color?: string | null; + imageData?: string | null; +} + +export interface UpdatePersonaInput { + personaName?: string | null; + description?: string | null; + voiceStyles?: string | null; + howToRespond?: string | null; + typoPercent?: number | null; + breakupLargeMessagePercent?: number | null; + responseTime?: string | null; + responseDelay?: number | null; + aiProviderPreferences?: string[] | null; + folderId?: string | null; + favorited?: boolean | null; + trash?: boolean | null; + default?: boolean | null; + color?: string | null; + imageData?: string | null; +} + +// ---------- Smart FAQ ---------- + +export interface SmartFAQDto { + id?: string | null; + agencyId?: string | null; + sourceId?: string | null; + question?: string | null; + answer?: string | null; + leadIds?: string[] | null; + state?: number; +} + +export interface CreateSmartFAQRequest { + sourceId?: string | null; + question?: string | null; + answer?: string | null; +} + +export interface AnswerMultipleFAQsRequest { + faQs?: AnswerFAQRequest[] | null; +} + +export interface AnswerFAQRequest { + id?: string | null; + answer?: string | null; +} + +export interface AnsweredFAQFollowUpRequest { + faqId?: string | null; + leadIds?: string[] | null; +} + +// ---------- Source ---------- + +export interface SourceDto { + agencyId?: string | null; + sourceId?: string | null; + name?: string | null; + category?: string | null; + key?: string | null; + accessToken?: string | null; + address?: string | null; + connected?: boolean; + autoShutoff?: boolean; + gracefulGoodbye?: boolean; + bots?: SourceBotDto[] | null; + accountsWithAccess?: string[] | null; + isAvailabilityContactTimezone?: boolean; + respondWindows?: SourceAvailabilityDto[] | null; + doNotRespondWindows?: SourceDoNotRespondWindowDto[] | null; + summarizeAttachments?: boolean; + respondToReactions?: boolean; + markConversationAsUnread?: boolean; + webhookCallback?: string | null; + wallet?: SourceWalletDto; +} + +export interface SourceDtoPaginated { + total?: number; + results?: SourceDto[] | null; + page?: number; + pageSize?: number; +} + +export interface SourceBotDto { + id?: string | null; + botName?: string | null; + tags?: ContactTag[] | null; + channels?: string[] | null; + personaNameOverride?: string | null; + enabled?: boolean; +} + +export interface SourceAvailabilityDto { + dayOfWeekUtc?: string | null; + startTimeUtc?: string | null; + duration?: string | null; +} + +export interface SourceDoNotRespondWindowDto { + start?: string; + end?: string; +} + +export interface SourceWalletDto { + reBilling?: boolean; + autoRefill?: boolean; + topUpAmount?: number; + refillThreshold?: number; + stripeCustomerId?: string | null; + currency?: string | null; + responseUnitCostOverride?: string | null; + storageUnitCostOverride?: string | null; + userUnitCostOverride?: string | null; +} + +export interface AddSourceInput { + name?: string | null; + category?: string | null; + key?: string | null; + accessToken?: string | null; + refreshToken?: string | null; + expiresIn?: number | null; + autoShutoff?: boolean | null; + gracefulGoodbye?: boolean | null; + summarizeAttachments?: boolean | null; + webhookCallback?: string | null; +} + +export interface UpdateSourceInput { + name?: string | null; + autoShutoff?: boolean | null; + gracefulGoodbye?: boolean | null; + isAvailabilityContactTimezone?: boolean | null; + respondWindows?: SourceAvailabilityDto[] | null; + doNotRespondWindows?: SourceDoNotRespondWindowDto[] | null; + summarizeAttachments?: boolean | null; + respondToReactions?: boolean | null; + markConversationsAsUnread?: boolean | null; + wallet?: UpdateSourceWalletInput; + accountsWithAccess?: string[] | null; + webhookCallback?: string | null; +} + +export interface UpdateSourceWalletInput { + reBilling?: boolean | null; + autoRefill?: boolean | null; + topUpAmount?: number | null; + refillThreshold?: number | null; + responseUnitCostOverride?: string | null; + storageUnitCostOverride?: string | null; + userUnitCostOverride?: string | null; + stripeCustomerId?: string | null; +} + +export interface SourceCalendarDto { + name?: string | null; + id?: string | null; +} + +export interface SourceChannelDto { + name?: string | null; + id?: string | null; +} + +export interface SourceFieldCollectionDto { + contact?: SourceFieldDto[] | null; + location?: SourceFieldDto[] | null; + customValue?: SourceFieldDto[] | null; +} + +export interface SourceFieldDto { + name?: string | null; + fieldKey?: string | null; + dataType?: string | null; +} + +export interface SourceTagDto { + name?: string | null; + id?: string | null; +} + +// ---------- Node Descriptors ---------- + +export interface NodeInformation { + dataTypes?: DataTypeInfo[] | null; + atomicNodes?: NodeInfo[] | null; + groups?: BotNodeGroup[] | null; + tools?: ToolInfo[] | null; +} + +export interface DataTypeInfo { + dataTypeName?: string | null; + properties?: PropertyInfo[] | null; + displayName?: string | null; +} + +export interface NodeInfo { + className?: string | null; + displayName?: string | null; + description?: string | null; + group?: string | null; + properties?: PropertyInfo[] | null; + outputs?: OutputInfo[] | null; + outputHandles?: OutputHandleInfo[] | null; + dynamicHandles?: DynamicOutputHandleInfo[] | null; + hasInputHandle?: boolean; + hasDynamicVariables?: boolean; + helpUrl?: string | null; + requiresPaid?: boolean; + order?: number; + hidden?: boolean; +} + +export interface PropertyInfo { + name?: string | null; + type?: string | null; + enumValues?: string[] | null; + displayName?: string | null; + defaultValue?: string | null; + group?: string | null; + conditions?: BotNodePropertyCondition[] | null; +} + +export interface BotNodePropertyCondition { + key?: string | null; + value?: string | null; +} + +export interface OutputInfo { + name?: string | null; + displayName?: string | null; +} + +export interface OutputHandleInfo { + name?: string | null; + label?: string | null; + color?: string | null; +} + +export interface DynamicOutputHandleInfo { + linkedProperty?: string | null; + labelPropertyName?: string | null; +} + +export interface BotNodeGroup { + name?: string | null; + order?: number; +} + +export interface ToolInfo { + className?: string | null; + displayName?: string | null; + description?: string | null; + properties?: PropertyInfo[] | null; + helpUrl?: string | null; + hidden?: boolean; +} + +// ---------- MCP Tool Registration Types ---------- + +export interface ToolDefinition { + name: string; + description: string; + inputSchema: { + type: "object"; + properties: Record; + required?: string[]; + }; +} + +export interface ToolGroup { + tools: ToolDefinition[]; + handler: (name: string, args: Record) => Promise; +} + +export interface ToolResult { + content: Array<{ type: "text"; text: string } | { type: "text"; text: string; [key: string]: unknown }>; + isError?: boolean; + structuredContent?: unknown; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..156b6d5 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "Node16", + "moduleResolution": "Node16", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +}