Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fd696382a |
1
.env.example
Normal file
1
.env.example
Normal file
@ -0,0 +1 @@
|
|||||||
|
CLOSEBOT_API_KEY=your_closebot_api_key_here
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
7
Dockerfile
Normal file
7
Dockerfile
Normal file
@ -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"]
|
||||||
87
README.md
Normal file
87
README.md
Normal file
@ -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
|
||||||
1713
package-lock.json
generated
Normal file
1713
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
package.json
Normal file
30
package.json
Normal file
@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
4
railway.json
Normal file
4
railway.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"build": { "builder": "NIXPACKS" },
|
||||||
|
"deploy": { "startCommand": "npm start" }
|
||||||
|
}
|
||||||
180
src/apps/analytics-dashboard.ts
Normal file
180
src/apps/analytics-dashboard.ts
Normal file
@ -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, "<").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 `<span style="color:${color};font-size:12px;">${sign}${pct.toFixed(1)}%</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => `
|
||||||
|
<div style="background:#1a1a2e;border-radius:12px;padding:16px;text-align:center;">
|
||||||
|
<div style="font-size:28px;margin-bottom:4px;">${c.icon}</div>
|
||||||
|
<div style="font-size:24px;font-weight:700;color:#fff;">${typeof c.value === "number" ? c.value.toLocaleString() : c.value}</div>
|
||||||
|
<div style="font-size:12px;color:#888;margin-top:4px;">${escapeHtml(c.label)}</div>
|
||||||
|
${c.change ? `<div style="margin-top:4px;">${c.change}</div>` : ""}
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
.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<string, unknown>) => (d.count as number) || 0), 1);
|
||||||
|
const bars = bookingData
|
||||||
|
.slice(-14) // last 14 data points
|
||||||
|
.map((d: Record<string, unknown>) => {
|
||||||
|
const val = (d.count as number) || 0;
|
||||||
|
const pct = (val / maxVal) * 100;
|
||||||
|
const label = d.label || d.date || d.key || "";
|
||||||
|
return `
|
||||||
|
<div style="display:flex;align-items:center;gap:8px;margin:2px 0;">
|
||||||
|
<div style="width:80px;font-size:11px;color:#888;text-align:right;">${escapeHtml(String(label))}</div>
|
||||||
|
<div style="flex:1;background:#111;border-radius:4px;height:20px;">
|
||||||
|
<div style="width:${pct}%;background:linear-gradient(90deg,#4ecdc4,#44a8a0);height:100%;border-radius:4px;min-width:2px;"></div>
|
||||||
|
</div>
|
||||||
|
<div style="width:40px;font-size:11px;color:#aaa;">${val}</div>
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
bookingChartHtml = `
|
||||||
|
<div style="margin-top:20px;">
|
||||||
|
<h3 style="color:#ccc;margin:0 0 12px;">📊 Booking Trend</h3>
|
||||||
|
${bars}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Metric data summary
|
||||||
|
let metricHtml = "";
|
||||||
|
if (metricData && typeof metricData === "object") {
|
||||||
|
metricHtml = `
|
||||||
|
<div style="margin-top:20px;">
|
||||||
|
<h3 style="color:#ccc;margin:0 0 8px;">📈 Response Metric Data</h3>
|
||||||
|
<pre style="background:#111;padding:12px;border-radius:8px;font-size:11px;color:#aaa;overflow-x:auto;max-height:200px;">${escapeHtml(JSON.stringify(metricData, null, 2).slice(0, 2000))}</pre>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<h2 style="margin:0 0 16px;color:#fff;">📊 Analytics Dashboard</h2>
|
||||||
|
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;">
|
||||||
|
${cardHtml}
|
||||||
|
</div>
|
||||||
|
${bookingChartHtml}
|
||||||
|
${metricHtml}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handler(
|
||||||
|
client: CloseBotClient,
|
||||||
|
name: string,
|
||||||
|
args: Record<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
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<AgencyDashboardSummaryResponse>("/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
135
src/apps/bot-dashboard.ts
Normal file
135
src/apps/bot-dashboard.ts
Normal file
@ -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, ">").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 `
|
||||||
|
<div style="background:#1a1a2e;border:1px solid #333;border-radius:12px;padding:16px;display:flex;flex-direction:column;gap:8px;">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;">
|
||||||
|
<span style="font-weight:600;font-size:15px;color:#e0e0e0;">${fav} ${escapeHtml(bot.name || "Unnamed")} ${locked}</span>
|
||||||
|
<span style="font-size:12px;color:#888;">${escapeHtml(category)}</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:12px;font-size:13px;color:#aaa;">
|
||||||
|
<span>${published} v${escapeHtml(versionLabel)}</span>
|
||||||
|
<span>📡 ${sourceCount} source${sourceCount !== 1 ? "s" : ""}</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:12px;color:#666;">Modified: ${escapeHtml(modified)}</div>
|
||||||
|
<div style="font-size:11px;color:#555;font-family:monospace;">ID: ${escapeHtml(bot.id || "")}</div>
|
||||||
|
${bot.followUpActive ? '<div style="font-size:11px;color:#4ecdc4;">↻ Follow-ups active</div>' : ""}
|
||||||
|
${bot.tools && bot.tools.length > 0 ? `<div style="font-size:11px;color:#8888ff;">🔧 ${bot.tools.length} tool(s)</div>` : ""}
|
||||||
|
</div>`;
|
||||||
|
}).join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<h2 style="margin:0 0 16px;color:#fff;">🤖 Bot Dashboard <span style="font-size:14px;color:#888;">(${bots.length} bots)</span></h2>
|
||||||
|
<div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(280px,1fr));gap:12px;">
|
||||||
|
${botCards}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderBotDetail(bot: BotDto): string {
|
||||||
|
const versions = (bot.versions || [])
|
||||||
|
.map(
|
||||||
|
(v) =>
|
||||||
|
`<tr><td style="padding:4px 12px;">${escapeHtml(v.version || "")}</td><td>${v.published ? "✅ Published" : "📝 Draft"}</td><td>${escapeHtml(v.name || "—")}</td><td>${v.modifiedAt ? new Date(v.modifiedAt).toLocaleString() : "—"}</td></tr>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const sources = (bot.sources || [])
|
||||||
|
.map(
|
||||||
|
(s) =>
|
||||||
|
`<tr><td style="padding:4px 12px;">${escapeHtml(s.name || "Unnamed")}</td><td>${escapeHtml(s.category || "—")}</td><td>${s.enabled ? "✅" : "❌"}</td><td style="font-family:monospace;font-size:11px;">${escapeHtml(s.id || "")}</td></tr>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<h2 style="margin:0 0 4px;color:#fff;">🤖 ${escapeHtml(bot.name || "Unnamed")}</h2>
|
||||||
|
<div style="font-size:12px;color:#666;margin-bottom:16px;font-family:monospace;">ID: ${escapeHtml(bot.id || "")}</div>
|
||||||
|
|
||||||
|
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px;">
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
|
||||||
|
<div style="font-size:24px;">${bot.sources?.length || 0}</div>
|
||||||
|
<div style="font-size:12px;color:#888;">Sources</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
|
||||||
|
<div style="font-size:24px;">${bot.versions?.length || 0}</div>
|
||||||
|
<div style="font-size:12px;color:#888;">Versions</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
|
||||||
|
<div style="font-size:24px;">${bot.locked ? "🔒" : "🔓"}</div>
|
||||||
|
<div style="font-size:12px;color:#888;">${bot.locked ? "Locked" : "Unlocked"}</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
|
||||||
|
<div style="font-size:24px;">${bot.followUpActive ? "✅" : "❌"}</div>
|
||||||
|
<div style="font-size:12px;color:#888;">Follow-ups</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style="color:#ccc;margin:16px 0 8px;">📋 Versions</h3>
|
||||||
|
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
||||||
|
<tr style="background:#1a1a2e;"><th style="text-align:left;padding:8px 12px;">Version</th><th>Status</th><th>Name</th><th>Modified</th></tr>
|
||||||
|
${versions || '<tr><td colspan="4" style="padding:8px;color:#666;">No versions</td></tr>'}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3 style="color:#ccc;margin:16px 0 8px;">📡 Sources</h3>
|
||||||
|
<table style="width:100%;border-collapse:collapse;font-size:13px;">
|
||||||
|
<tr style="background:#1a1a2e;"><th style="text-align:left;padding:8px 12px;">Name</th><th>Category</th><th>Enabled</th><th>ID</th></tr>
|
||||||
|
${sources || '<tr><td colspan="4" style="padding:8px;color:#666;">No sources attached</td></tr>'}
|
||||||
|
</table>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handler(
|
||||||
|
client: CloseBotClient,
|
||||||
|
name: string,
|
||||||
|
args: Record<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
if (args.botId) {
|
||||||
|
const bot = await client.get<BotDto>(`/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<BotDto[]>("/bot");
|
||||||
|
const html = renderBotGrid(bots);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: `Dashboard showing ${bots.length} bots` }],
|
||||||
|
structuredContent: { type: "html", html },
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
184
src/apps/lead-manager.ts
Normal file
184
src/apps/lead-manager.ts
Normal file
@ -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, ">").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))
|
||||||
|
: '<span style="color:#555;">—</span>';
|
||||||
|
const direction =
|
||||||
|
lead.lastMessageDirection === "outbound"
|
||||||
|
? '<span style="color:#4ecdc4;">→ out</span>'
|
||||||
|
: lead.lastMessageDirection === "inbound"
|
||||||
|
? '<span style="color:#ff9f43;">← in</span>'
|
||||||
|
: '<span style="color:#555;">—</span>';
|
||||||
|
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
|
||||||
|
? `<div style="font-size:10px;color:#ff6b6b;margin-top:2px;">⚠️ ${escapeHtml(lead.mostRecentFailureReason.slice(0, 40))}</div>`
|
||||||
|
: "";
|
||||||
|
const tags =
|
||||||
|
lead.tags && lead.tags.length > 0
|
||||||
|
? lead.tags
|
||||||
|
.slice(0, 3)
|
||||||
|
.map((t) => `<span style="background:#2a1a3e;padding:1px 6px;border-radius:4px;font-size:10px;">${escapeHtml(t)}</span>`)
|
||||||
|
.join(" ")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<tr style="border-bottom:1px solid #222;">
|
||||||
|
<td style="padding:10px 8px;">
|
||||||
|
<div style="font-weight:600;font-size:13px;">${escapeHtml(lead.name || "Unknown")}</div>
|
||||||
|
<div style="font-size:10px;color:#555;font-family:monospace;">${escapeHtml(lead.id || "")}</div>
|
||||||
|
${tags ? `<div style="margin-top:4px;">${tags}</div>` : ""}
|
||||||
|
</td>
|
||||||
|
<td style="padding:10px 8px;font-size:12px;color:#888;">${escapeHtml(source)}</td>
|
||||||
|
<td style="padding:10px 8px;font-size:12px;">
|
||||||
|
${direction}
|
||||||
|
<div style="font-size:11px;color:#aaa;margin-top:2px;">${lastMsg}</div>
|
||||||
|
${failReason}
|
||||||
|
</td>
|
||||||
|
<td style="padding:10px 8px;font-size:11px;color:#666;">${escapeHtml(time)}</td>
|
||||||
|
<td style="padding:10px 8px;text-align:center;font-size:12px;">${fieldCount}</td>
|
||||||
|
<td style="padding:10px 8px;text-align:center;font-size:12px;">${instanceCount}</td>
|
||||||
|
</tr>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<h2 style="margin:0 0 4px;color:#fff;">👥 Lead Manager</h2>
|
||||||
|
<div style="font-size:12px;color:#666;margin-bottom:16px;">
|
||||||
|
${data.total ?? leads.length} total leads · Page ${(data.page ?? 0) + 1} · ${data.pageSize ?? 20} per page
|
||||||
|
</div>
|
||||||
|
<div style="overflow-x:auto;">
|
||||||
|
<table style="width:100%;border-collapse:collapse;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background:#1a1a2e;">
|
||||||
|
<th style="text-align:left;padding:10px 8px;font-size:12px;color:#888;">Lead</th>
|
||||||
|
<th style="text-align:left;padding:10px 8px;font-size:12px;color:#888;">Source</th>
|
||||||
|
<th style="text-align:left;padding:10px 8px;font-size:12px;color:#888;">Last Message</th>
|
||||||
|
<th style="text-align:left;padding:10px 8px;font-size:12px;color:#888;">Time</th>
|
||||||
|
<th style="text-align:center;padding:10px 8px;font-size:12px;color:#888;">Fields</th>
|
||||||
|
<th style="text-align:center;padding:10px 8px;font-size:12px;color:#888;">Bots</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${rows || '<tr><td colspan="6" style="padding:40px;text-align:center;color:#666;">No leads found</td></tr>'}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderLeadDetail(lead: LeadDto): string {
|
||||||
|
const fields = (lead.fields || [])
|
||||||
|
.map(
|
||||||
|
(f) =>
|
||||||
|
`<tr><td style="padding:4px 8px;color:#888;font-size:12px;">${escapeHtml(f.field || "")}</td><td style="padding:4px 8px;font-size:12px;">${escapeHtml(f.value || "—")}</td></tr>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const instances = (lead.instances || [])
|
||||||
|
.map(
|
||||||
|
(i) =>
|
||||||
|
`<tr><td style="padding:4px 8px;font-size:12px;font-family:monospace;">${escapeHtml(i.botId || "")}</td><td style="padding:4px 8px;font-size:12px;">v${escapeHtml(i.botVersion || "?")}</td><td style="padding:4px 8px;font-size:12px;">${i.followUpTime ? new Date(i.followUpTime).toLocaleString() : "—"}</td></tr>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const tags = (lead.tags || [])
|
||||||
|
.map((t) => `<span style="background:#2a1a3e;padding:2px 8px;border-radius:6px;font-size:11px;margin:2px;">${escapeHtml(t)}</span>`)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<h2 style="margin:0 0 4px;color:#fff;">👤 ${escapeHtml(lead.name || "Unknown Lead")}</h2>
|
||||||
|
<div style="font-size:12px;color:#555;font-family:monospace;margin-bottom:16px;">${escapeHtml(lead.id || "")}</div>
|
||||||
|
|
||||||
|
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:20px;">
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;">
|
||||||
|
<div style="font-size:11px;color:#888;">Source</div>
|
||||||
|
<div style="font-size:14px;margin-top:4px;">${escapeHtml(lead.source?.name || "—")}</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;">
|
||||||
|
<div style="font-size:11px;color:#888;">Last Message</div>
|
||||||
|
<div style="font-size:12px;margin-top:4px;color:#aaa;">${escapeHtml((lead.lastMessage || "—").slice(0, 80))}</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;">
|
||||||
|
<div style="font-size:11px;color:#888;">Contact ID</div>
|
||||||
|
<div style="font-size:12px;margin-top:4px;font-family:monospace;">${escapeHtml(lead.contactId || "—")}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${tags ? `<div style="margin-bottom:16px;">${tags}</div>` : ""}
|
||||||
|
|
||||||
|
${lead.mostRecentFailureReason ? `<div style="background:#2a1a1a;border:1px solid #5a2a2a;border-radius:8px;padding:12px;margin-bottom:16px;font-size:12px;color:#ff6b6b;">⚠️ ${escapeHtml(lead.mostRecentFailureReason)}</div>` : ""}
|
||||||
|
|
||||||
|
<h3 style="color:#ccc;margin:16px 0 8px;">📋 Fields (${lead.fields?.length || 0})</h3>
|
||||||
|
<table style="width:100%;border-collapse:collapse;">
|
||||||
|
${fields || '<tr><td style="padding:8px;color:#666;">No fields</td></tr>'}
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<h3 style="color:#ccc;margin:16px 0 8px;">🤖 Bot Instances (${lead.instances?.length || 0})</h3>
|
||||||
|
<table style="width:100%;border-collapse:collapse;">
|
||||||
|
<tr style="background:#1a1a2e;"><th style="text-align:left;padding:6px 8px;font-size:11px;">Bot ID</th><th>Version</th><th>Follow-up</th></tr>
|
||||||
|
${instances || '<tr><td colspan="3" style="padding:8px;color:#666;">No instances</td></tr>'}
|
||||||
|
</table>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handler(
|
||||||
|
client: CloseBotClient,
|
||||||
|
name: string,
|
||||||
|
args: Record<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
if (args.leadId) {
|
||||||
|
const lead = await client.get<LeadDto>(`/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<LeadDtoPaginated>("/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/apps/leaderboard.ts
Normal file
157
src/apps/leaderboard.ts
Normal file
@ -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, ">")
|
||||||
|
.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
|
||||||
|
? ' <span style="background:#4299e1;color:#fff;padding:1px 6px;border-radius:8px;font-size:11px">YOU</span>'
|
||||||
|
: "";
|
||||||
|
return `<tr${isYou}><td style="text-align:center;font-size:18px">${medal}</td><td>${name}${youBadge}</td><td style="text-align:right;font-variant-numeric:tabular-nums">${value.toLocaleString()}</td></tr>`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
return `<div style="font-family:system-ui,-apple-system,sans-serif;background:#1a202c;color:#e2e8f0;padding:20px;border-radius:12px;max-width:600px">
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:16px">
|
||||||
|
<div>
|
||||||
|
<h2 style="margin:0;font-size:20px;color:#fff">${scopeLabel} Leaderboard</h2>
|
||||||
|
<span style="color:#a0aec0;font-size:13px">${metricLabel} · ${dateRange}</span>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:6px">
|
||||||
|
<span style="background:${metric === "responses" ? "#4299e1" : "#2d3748"};color:#fff;padding:4px 10px;border-radius:6px;font-size:12px;cursor:pointer">Responses</span>
|
||||||
|
<span style="background:${metric === "bookings" ? "#48bb78" : "#2d3748"};color:#fff;padding:4px 10px;border-radius:6px;font-size:12px;cursor:pointer">Bookings</span>
|
||||||
|
<span style="background:${metric === "contacts" ? "#ed8936" : "#2d3748"};color:#fff;padding:4px 10px;border-radius:6px;font-size:12px;cursor:pointer">Contacts</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table style="width:100%;border-collapse:collapse">
|
||||||
|
<thead>
|
||||||
|
<tr style="border-bottom:1px solid #4a5568">
|
||||||
|
<th style="text-align:center;padding:8px 4px;color:#a0aec0;font-size:12px;width:50px">Rank</th>
|
||||||
|
<th style="text-align:left;padding:8px;color:#a0aec0;font-size:12px">Agency</th>
|
||||||
|
<th style="text-align:right;padding:8px;color:#a0aec0;font-size:12px">${metricLabel}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${rows || '<tr><td colspan="3" style="text-align:center;padding:20px;color:#718096">No leaderboard data available</td></tr>'}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<div style="margin-top:12px;text-align:center;color:#718096;font-size:11px">${entries.length} agencies shown</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handle(
|
||||||
|
client: CloseBotClient,
|
||||||
|
name: string,
|
||||||
|
args: Record<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
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<LeaderboardResponse[]>(
|
||||||
|
"/botMetric/localleaderboard",
|
||||||
|
{
|
||||||
|
metric,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
numSurroundingAgencies: args.numSurrounding ?? 5,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
entries = await client.get<LeaderboardResponse[]>(
|
||||||
|
"/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
192
src/apps/library-manager.ts
Normal file
192
src/apps/library-manager.ts
Normal file
@ -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, ">").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 '<span style="background:#1a3a2a;color:#4ecdc4;padding:2px 8px;border-radius:4px;font-size:10px;">✅ Ready</span>';
|
||||||
|
if (s === "processing" || s === "pending")
|
||||||
|
return '<span style="background:#3a3a1a;color:#ffd93d;padding:2px 8px;border-radius:4px;font-size:10px;">⏳ Processing</span>';
|
||||||
|
if (s === "error" || s === "failed")
|
||||||
|
return '<span style="background:#3a1a1a;color:#ff6b6b;padding:2px 8px;border-radius:4px;font-size:10px;">❌ Error</span>';
|
||||||
|
return `<span style="background:#1a1a2e;color:#888;padding:2px 8px;border-radius:4px;font-size:10px;">${escapeHtml(status || "Unknown")}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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) =>
|
||||||
|
`<span style="background:#1a2a3e;padding:1px 6px;border-radius:4px;font-size:10px;color:#7ec8e3;">${escapeHtml(s.name || s.id || "")}</span>`
|
||||||
|
)
|
||||||
|
.join(" ");
|
||||||
|
const modified = f.lastModified
|
||||||
|
? new Date(f.lastModified).toLocaleDateString()
|
||||||
|
: "—";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="background:#1a1a2e;border-radius:10px;padding:14px;display:flex;align-items:center;gap:14px;">
|
||||||
|
<div style="font-size:28px;width:36px;text-align:center;">${icon}</div>
|
||||||
|
<div style="flex:1;min-width:0;">
|
||||||
|
<div style="font-weight:600;font-size:13px;color:#e0e0e0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">
|
||||||
|
${escapeHtml(f.fileName || "Unnamed")}
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:8px;align-items:center;margin-top:4px;">
|
||||||
|
${statusBadge(f.fileStatus)}
|
||||||
|
<span style="font-size:11px;color:#666;">${escapeHtml(f.fileType || "")}</span>
|
||||||
|
<span style="font-size:11px;color:#666;">${formatSize(f.fileSize || 0)}</span>
|
||||||
|
<span style="font-size:11px;color:#666;">${escapeHtml(modified)}</span>
|
||||||
|
</div>
|
||||||
|
${sources ? `<div style="margin-top:6px;display:flex;gap:4px;flex-wrap:wrap;">${sources}</div>` : ""}
|
||||||
|
</div>
|
||||||
|
<div style="font-size:10px;color:#444;font-family:monospace;white-space:nowrap;">${escapeHtml(f.fileId || "")}</div>
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<h2 style="margin:0 0 4px;color:#fff;">📚 Library Manager</h2>
|
||||||
|
<div style="font-size:12px;color:#666;margin-bottom:16px;">
|
||||||
|
${files.length} files · ${formatSize(totalSize)} total
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:8px;">
|
||||||
|
${rows || '<div style="padding:40px;text-align:center;color:#666;">No files. Use upload_file or create_web_scrape to add content.</div>'}
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:12px;font-size:11px;color:#555;">
|
||||||
|
💡 Use <code>upload_file</code> to upload, <code>create_web_scrape</code> to scrape websites, <code>attach_file_to_source</code> to connect to sources
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderFileDetail(file: FileDto, scrapePages: Array<{ url?: string; enabled?: boolean }>): string {
|
||||||
|
const sources = (file.sources || [])
|
||||||
|
.map(
|
||||||
|
(s) =>
|
||||||
|
`<div style="background:#1a2a3e;padding:8px 12px;border-radius:6px;display:flex;justify-content:space-between;">
|
||||||
|
<span style="font-size:12px;color:#7ec8e3;">${escapeHtml(s.name || "Unnamed")}</span>
|
||||||
|
<span style="font-size:10px;color:#666;">${escapeHtml(s.category || "")} · ${escapeHtml(s.id || "")}</span>
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const pages = scrapePages
|
||||||
|
.map(
|
||||||
|
(p) =>
|
||||||
|
`<div style="display:flex;align-items:center;gap:8px;padding:4px 0;">
|
||||||
|
<span style="font-size:14px;">${p.enabled ? "✅" : "❌"}</span>
|
||||||
|
<span style="font-size:12px;color:#aaa;word-break:break-all;">${escapeHtml(p.url || "")}</span>
|
||||||
|
</div>`
|
||||||
|
)
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px;">
|
||||||
|
<span style="font-size:36px;">${fileIcon(file.fileType)}</span>
|
||||||
|
<div>
|
||||||
|
<h2 style="margin:0;color:#fff;">${escapeHtml(file.fileName || "Unnamed")}</h2>
|
||||||
|
<div style="font-size:11px;color:#555;font-family:monospace;">${escapeHtml(file.fileId || "")}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:20px;">
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
|
||||||
|
<div style="font-size:11px;color:#888;">Status</div>
|
||||||
|
<div style="margin-top:4px;">${statusBadge(file.fileStatus)}</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
|
||||||
|
<div style="font-size:11px;color:#888;">Type</div>
|
||||||
|
<div style="font-size:13px;margin-top:4px;">${escapeHtml(file.fileType || "—")}</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
|
||||||
|
<div style="font-size:11px;color:#888;">Size</div>
|
||||||
|
<div style="font-size:13px;margin-top:4px;">${formatSize(file.fileSize || 0)}</div>
|
||||||
|
</div>
|
||||||
|
<div style="background:#1a1a2e;padding:12px;border-radius:8px;text-align:center;">
|
||||||
|
<div style="font-size:11px;color:#888;">Modified</div>
|
||||||
|
<div style="font-size:13px;margin-top:4px;">${file.lastModified ? new Date(file.lastModified).toLocaleDateString() : "—"}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 style="color:#ccc;margin:16px 0 8px;">📡 Attached Sources (${file.sources?.length || 0})</h3>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:4px;">
|
||||||
|
${sources || '<div style="color:#666;font-size:12px;">Not attached to any sources</div>'}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${scrapePages.length > 0 ? `
|
||||||
|
<h3 style="color:#ccc;margin:16px 0 8px;">🌐 Scrape Pages (${scrapePages.length})</h3>
|
||||||
|
<div style="background:#111;border-radius:8px;padding:12px;max-height:300px;overflow-y:auto;">
|
||||||
|
${pages}
|
||||||
|
</div>
|
||||||
|
` : ""}
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handler(
|
||||||
|
client: CloseBotClient,
|
||||||
|
name: string,
|
||||||
|
args: Record<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
if (args.fileId) {
|
||||||
|
const [file, scrapePages] = await Promise.all([
|
||||||
|
client.get<FileDto>(`/library/files/${args.fileId}`),
|
||||||
|
client.get<Array<{ url?: string; enabled?: boolean }>>(`/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<FileDto[]>("/library/files");
|
||||||
|
const html = renderFileList(files);
|
||||||
|
return {
|
||||||
|
content: [{ type: "text", text: `Library: ${files.length} files` }],
|
||||||
|
structuredContent: { type: "html", html },
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
145
src/apps/test-console.ts
Normal file
145
src/apps/test-console.ts
Normal file
@ -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, ">").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 `
|
||||||
|
<div style="background:#1a1a2e;border-radius:8px;padding:12px;display:flex;flex-direction:column;gap:4px;">
|
||||||
|
<div style="display:flex;justify-content:space-between;">
|
||||||
|
<span style="font-weight:600;color:#e0e0e0;font-size:13px;">${escapeHtml(lead.name || "Test Lead")}</span>
|
||||||
|
<span style="font-size:11px;color:#666;">${time}</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:12px;color:#888;">${direction} ${lastMsg}${(lead.lastMessage?.length ?? 0) > 60 ? "…" : ""}</div>
|
||||||
|
<div style="font-size:10px;color:#555;font-family:monospace;">Lead: ${escapeHtml(lead.id || "")}</div>
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<h2 style="margin:0 0 4px;color:#fff;">🧪 Test Console</h2>
|
||||||
|
<div style="font-size:12px;color:#666;margin-bottom:16px;">Bot: <code>${escapeHtml(botId)}</code> · ${sessions.total ?? leads.length} session(s)</div>
|
||||||
|
<div style="display:flex;flex-direction:column;gap:8px;">
|
||||||
|
${rows || '<div style="color:#666;padding:20px;text-align:center;">No test sessions found. Create one with create_test_session.</div>'}
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
? `<div style="font-size:10px;color:#666;margin-top:4px;">Activities: ${msg.activities.map((a) => escapeHtml(a.activity || "")).join(", ")}</div>`
|
||||||
|
: "";
|
||||||
|
const attachments =
|
||||||
|
msg.attachments && msg.attachments.length > 0
|
||||||
|
? `<div style="font-size:10px;color:#4ecdc4;margin-top:4px;">📎 ${msg.attachments.length} attachment(s)</div>`
|
||||||
|
: "";
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="display:flex;justify-content:${align};margin:4px 0;">
|
||||||
|
<div style="max-width:75%;background:${bg};border:1px solid ${border};border-radius:12px;padding:10px 14px;">
|
||||||
|
<div style="display:flex;justify-content:space-between;gap:16px;margin-bottom:4px;">
|
||||||
|
<span style="font-size:11px;font-weight:600;color:#aaa;">${label}</span>
|
||||||
|
<span style="font-size:10px;color:#555;">${escapeHtml(time)}</span>
|
||||||
|
</div>
|
||||||
|
<div style="font-size:13px;color:#e0e0e0;white-space:pre-wrap;">${escapeHtml(content)}</div>
|
||||||
|
${activities}${attachments}
|
||||||
|
<div style="font-size:9px;color:#444;margin-top:4px;font-family:monospace;">msg: ${escapeHtml(msg.messageId || "")}</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
})
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<div style="font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#0d0d1a;color:#e0e0e0;padding:20px;border-radius:16px;">
|
||||||
|
<h2 style="margin:0 0 4px;color:#fff;">🧪 Test Conversation</h2>
|
||||||
|
<div style="font-size:12px;color:#666;margin-bottom:16px;">
|
||||||
|
Bot: <code>${escapeHtml(botId)}</code> · Lead: <code>${escapeHtml(leadId)}</code> · ${sorted.length} messages
|
||||||
|
</div>
|
||||||
|
<div style="background:#111;border-radius:12px;padding:16px;max-height:600px;overflow-y:auto;">
|
||||||
|
${msgHtml || '<div style="color:#666;text-align:center;padding:40px;">No messages yet. Send one with send_test_message.</div>'}
|
||||||
|
</div>
|
||||||
|
<div style="margin-top:12px;display:flex;gap:8px;font-size:11px;color:#888;">
|
||||||
|
<span>💡 Use <code>send_test_message</code> to send messages</span>
|
||||||
|
<span>· <code>force_test_step</code> to advance</span>
|
||||||
|
<span>· <code>rollback_test_session</code> to undo</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handler(
|
||||||
|
client: CloseBotClient,
|
||||||
|
name: string,
|
||||||
|
args: Record<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
const botId = args.botId as string;
|
||||||
|
const leadId = args.leadId as string | undefined;
|
||||||
|
|
||||||
|
if (leadId) {
|
||||||
|
const messages = await client.get<BotMetricMessage[]>("/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<ListLeadDto>(`/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
210
src/client.ts
Normal file
210
src/client.ts
Normal file
@ -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, unknown>): 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<string, string> {
|
||||||
|
return {
|
||||||
|
"X-CB-KEY": this.apiKey,
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Accept: "application/json",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async get<T = unknown>(
|
||||||
|
path: string,
|
||||||
|
query?: Record<string, unknown>
|
||||||
|
): Promise<T> {
|
||||||
|
const url = this.buildUrl(path, query);
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: this.headers,
|
||||||
|
});
|
||||||
|
return this.handleResponse<T>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async post<T = unknown>(
|
||||||
|
path: string,
|
||||||
|
body?: unknown,
|
||||||
|
query?: Record<string, unknown>
|
||||||
|
): Promise<T> {
|
||||||
|
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<T>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async put<T = unknown>(
|
||||||
|
path: string,
|
||||||
|
body?: unknown,
|
||||||
|
query?: Record<string, unknown>
|
||||||
|
): Promise<T> {
|
||||||
|
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<T>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete<T = unknown>(
|
||||||
|
path: string,
|
||||||
|
query?: Record<string, unknown>
|
||||||
|
): Promise<T> {
|
||||||
|
const url = this.buildUrl(path, query);
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "DELETE",
|
||||||
|
headers: this.headers,
|
||||||
|
});
|
||||||
|
return this.handleResponse<T>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async postFormData<T = unknown>(
|
||||||
|
path: string,
|
||||||
|
formData: FormData,
|
||||||
|
query?: Record<string, unknown>
|
||||||
|
): Promise<T> {
|
||||||
|
const url = this.buildUrl(path, query);
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
"X-CB-KEY": this.apiKey,
|
||||||
|
Accept: "application/json",
|
||||||
|
};
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "POST",
|
||||||
|
headers,
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
return this.handleResponse<T>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
async putFormData<T = unknown>(
|
||||||
|
path: string,
|
||||||
|
formData: FormData,
|
||||||
|
query?: Record<string, unknown>
|
||||||
|
): Promise<T> {
|
||||||
|
const url = this.buildUrl(path, query);
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
"X-CB-KEY": this.apiKey,
|
||||||
|
Accept: "application/json",
|
||||||
|
};
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "PUT",
|
||||||
|
headers,
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
return this.handleResponse<T>(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleResponse<T>(response: Response): Promise<T> {
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
156
src/index.ts
Normal file
156
src/index.ts
Normal file
@ -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<string, unknown>
|
||||||
|
) => Promise<ToolResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string, number>();
|
||||||
|
// All tool definitions (populated on init)
|
||||||
|
let allTools: ToolDefinition[] = [];
|
||||||
|
|
||||||
|
async function loadGroupMetadata(): Promise<void> {
|
||||||
|
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<ToolModule["handle"] | null> {
|
||||||
|
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<void> {
|
||||||
|
// 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<string, unknown>;
|
||||||
|
}
|
||||||
|
const result = await handler(client, name, (args as Record<string, unknown>) || {});
|
||||||
|
return result as unknown as Record<string, unknown>;
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- 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);
|
||||||
|
});
|
||||||
353
src/tools/agency-billing.ts
Normal file
353
src/tools/agency-billing.ts
Normal file
@ -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<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
case "get_agency":
|
||||||
|
return ok(await client.get<AgencyDto>("/agency/current"));
|
||||||
|
|
||||||
|
case "list_agencies":
|
||||||
|
return ok(await client.get<AgencyDto[]>("/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<BalanceDto>("/agency/billing/balance"));
|
||||||
|
|
||||||
|
case "get_source_balance":
|
||||||
|
return ok(
|
||||||
|
await client.get<BalanceDto>(
|
||||||
|
`/agency/billing/balance/source/${args.sourceId}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
case "get_billing_options":
|
||||||
|
return ok(
|
||||||
|
await client.get<BillingOptionsDto>("/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<BillingOptionsDto>("/agency/billing/options", body)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
case "list_transactions":
|
||||||
|
return ok(
|
||||||
|
await client.get<TransactionDto[]>("/agency/billing/transactions", {
|
||||||
|
status: args.status,
|
||||||
|
page: args.page,
|
||||||
|
pageSize: args.pageSize,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case "list_source_transactions":
|
||||||
|
return ok(
|
||||||
|
await client.get<TransactionDto[]>(
|
||||||
|
`/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<TransactionDto>(
|
||||||
|
`/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<BilledUsageDto[]>("/agency/billing/usages", {
|
||||||
|
startTime: args.startTime,
|
||||||
|
endTime: args.endTime,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case "get_rebilling":
|
||||||
|
return ok(
|
||||||
|
await client.get<ReBillingDto>("/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
344
src/tools/analytics.ts
Normal file
344
src/tools/analytics.ts
Normal file
@ -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<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
case "get_agency_summary":
|
||||||
|
return ok(
|
||||||
|
await client.get<AgencyDashboardSummaryResponse>("/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<number>("/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<BotMetricAction[]>("/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<number>("/botMetric/messageCount", {
|
||||||
|
sourceId: args.sourceId,
|
||||||
|
leadId: args.leadId,
|
||||||
|
start: args.start,
|
||||||
|
end: args.end,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case "get_messages":
|
||||||
|
return ok(
|
||||||
|
await client.get<BotMetricMessage[]>("/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<MessageFeedbackResponse>("/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<MessageLikesResponse>("/botMetric/messageLikes", {
|
||||||
|
leadId: args.leadId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case "get_message_reason":
|
||||||
|
return ok(
|
||||||
|
await client.get<MessageReason>("/botMetric/messageReason", {
|
||||||
|
messageId: args.messageId,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case "get_leaderboard":
|
||||||
|
return ok(
|
||||||
|
await client.get<LeaderboardResponse[]>("/botMetric/leaderboard", {
|
||||||
|
metric: args.metric,
|
||||||
|
start: args.start,
|
||||||
|
end: args.end,
|
||||||
|
numTopLeaders: args.numTopLeaders,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case "get_local_leaderboard":
|
||||||
|
return ok(
|
||||||
|
await client.get<LeaderboardResponse[]>("/botMetric/localleaderboard", {
|
||||||
|
metric: args.metric,
|
||||||
|
start: args.start,
|
||||||
|
end: args.end,
|
||||||
|
numSurroundingAgencies: args.numSurroundingAgencies,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case "get_logs":
|
||||||
|
return ok(
|
||||||
|
await client.get<BotMetricLog[]>("/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
353
src/tools/bot-management.ts
Normal file
353
src/tools/bot-management.ts
Normal file
@ -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<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
case "list_bots":
|
||||||
|
return ok(await client.get<BotDto[]>("/bot"));
|
||||||
|
|
||||||
|
case "get_bot":
|
||||||
|
return ok(await client.get<BotDto>(`/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<BotDto>("/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<BotDto>("/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<BotDto>(`/bot/${id}`, body));
|
||||||
|
}
|
||||||
|
|
||||||
|
case "delete_bot":
|
||||||
|
return ok(await client.delete(`/bot/${args.id}`));
|
||||||
|
|
||||||
|
case "duplicate_bot":
|
||||||
|
return ok(await client.post<BotDto>(`/bot/${args.id}/duplicate`));
|
||||||
|
|
||||||
|
case "export_bot":
|
||||||
|
return ok(
|
||||||
|
await client.get<ExportBotResponse>(`/bot/${args.id}/export`, {
|
||||||
|
botVersion: args.botVersion,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
case "publish_bot":
|
||||||
|
return ok(await client.post<PublishBotResponse>(`/bot/${args.id}/publish`));
|
||||||
|
|
||||||
|
case "save_bot": {
|
||||||
|
const body: SaveBotInput = { botSteps: args.botSteps };
|
||||||
|
return ok(await client.post<SaveBotResponse>(`/bot/${args.id}/save`, body));
|
||||||
|
}
|
||||||
|
|
||||||
|
case "save_bot_tools":
|
||||||
|
return ok(
|
||||||
|
await client.post<BotToolDto[]>(
|
||||||
|
`/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<BotTemplateDto[]>("/botTemplates"));
|
||||||
|
|
||||||
|
case "attach_source_to_bot": {
|
||||||
|
const body: Record<string, unknown> = {};
|
||||||
|
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<string[]>("/bot/bbb/templates"));
|
||||||
|
|
||||||
|
default:
|
||||||
|
return err(`Unknown tool: ${name}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
166
src/tools/bot-testing.ts
Normal file
166
src/tools/bot-testing.ts
Normal file
@ -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<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
case "create_test_session":
|
||||||
|
return ok(
|
||||||
|
await client.post<TestSession>(`/bot/${args.botId}/testSession`)
|
||||||
|
);
|
||||||
|
|
||||||
|
case "list_test_sessions":
|
||||||
|
return ok(
|
||||||
|
await client.get<ListLeadDto>(`/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<UpdateSessionDto>(
|
||||||
|
`/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<BotTestingRollbackOutput>(
|
||||||
|
`/bot/${args.botId}/testSession/${args.leadId}/rollback`,
|
||||||
|
body
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return err(`Unknown tool: ${name}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
539
src/tools/configuration.ts
Normal file
539
src/tools/configuration.ts
Normal file
@ -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<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
// Personas
|
||||||
|
case "list_personas":
|
||||||
|
return ok(await client.get<PersonaDto[]>("/persona"));
|
||||||
|
case "get_persona":
|
||||||
|
return ok(await client.get<PersonaDto>(`/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<string, unknown>)[k] = v;
|
||||||
|
}
|
||||||
|
return ok(await client.put<PersonaDto>(`/persona/${id}`, body));
|
||||||
|
}
|
||||||
|
case "delete_persona":
|
||||||
|
return ok(await client.delete(`/persona/${args.id}`));
|
||||||
|
|
||||||
|
// FAQs
|
||||||
|
case "list_faqs":
|
||||||
|
return ok(
|
||||||
|
await client.get<SmartFAQDto[]>("/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<SmartFAQDto[]>("/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<ListHierarchyResult[]>("/hierarchy"));
|
||||||
|
case "get_folder":
|
||||||
|
return ok(await client.get<ListHierarchyResult>(`/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<NotificationDto[]>("/notifications"));
|
||||||
|
case "update_notification": {
|
||||||
|
const body: NotificationUpdateDto = { viewed: args.viewed as boolean };
|
||||||
|
return ok(await client.put<NotificationDto>(`/notifications/${args.id}`, body));
|
||||||
|
}
|
||||||
|
case "delete_notification":
|
||||||
|
return ok(await client.delete<NotificationDto>(`/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<ApiKeyDTO[]>("/account/apiKey"));
|
||||||
|
case "create_api_key": {
|
||||||
|
const body: CreateApiKeyInput = { name: args.name as string };
|
||||||
|
return ok(await client.post<CreateApiKeyOutput>("/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<LiveDemoDto[]>(`/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<LiveDemoDto>(`/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<LiveDemoDto>(`/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<BotVariableDto[]>(
|
||||||
|
`/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
157
src/tools/lead-management.ts
Normal file
157
src/tools/lead-management.ts
Normal file
@ -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<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
case "list_leads":
|
||||||
|
return ok(
|
||||||
|
await client.get<LeadDtoPaginated>("/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<LeadDto[]>("/lead/search", body));
|
||||||
|
}
|
||||||
|
|
||||||
|
case "get_lead":
|
||||||
|
return ok(await client.get<LeadDto>(`/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
232
src/tools/library.ts
Normal file
232
src/tools/library.ts
Normal file
@ -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<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
case "list_files":
|
||||||
|
return ok(await client.get<FileDto[]>("/library/files"));
|
||||||
|
|
||||||
|
case "get_file":
|
||||||
|
return ok(await client.get<FileDto>(`/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<WebscrapePageDto[]>(
|
||||||
|
`/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);
|
||||||
|
}
|
||||||
|
}
|
||||||
213
src/tools/source-management.ts
Normal file
213
src/tools/source-management.ts
Normal file
@ -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<string, unknown>
|
||||||
|
): Promise<ToolResult> {
|
||||||
|
try {
|
||||||
|
switch (name) {
|
||||||
|
case "list_sources":
|
||||||
|
return ok(
|
||||||
|
await client.get<SourceDtoPaginated>("/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<SourceDto>(`/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<SourceDto>("/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<SourceDto>(`/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<SourceCalendarDto[]>(`/agency/source/${args.id}/calendars`)
|
||||||
|
);
|
||||||
|
|
||||||
|
case "list_source_channels":
|
||||||
|
return ok(
|
||||||
|
await client.get<SourceChannelDto[]>(`/agency/source/${args.id}/channels`)
|
||||||
|
);
|
||||||
|
|
||||||
|
case "list_source_fields":
|
||||||
|
return ok(
|
||||||
|
await client.get<SourceFieldCollectionDto>(`/agency/source/${args.id}/fields`)
|
||||||
|
);
|
||||||
|
|
||||||
|
case "list_source_tags":
|
||||||
|
return ok(
|
||||||
|
await client.get<SourceTagDto[]>(`/agency/source/${args.id}/tags`)
|
||||||
|
);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return err(`Unknown tool: ${name}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return err(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
940
src/types.ts
Normal file
940
src/types.ts
Normal file
@ -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<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<string, unknown>;
|
||||||
|
required?: string[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolGroup {
|
||||||
|
tools: ToolDefinition[];
|
||||||
|
handler: (name: string, args: Record<string, unknown>) => Promise<ToolResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolResult {
|
||||||
|
content: Array<{ type: "text"; text: string } | { type: "text"; text: string; [key: string]: unknown }>;
|
||||||
|
isError?: boolean;
|
||||||
|
structuredContent?: unknown;
|
||||||
|
}
|
||||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
@ -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"]
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user