Compare commits
No commits in common. "main" and "gh-pages" have entirely different histories.
@ -1 +0,0 @@
|
||||
CLOSEBOT_API_KEY=your_closebot_api_key_here
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@ -1,4 +0,0 @@
|
||||
node_modules/
|
||||
dist/
|
||||
.env
|
||||
*.log
|
||||
@ -1,7 +0,0 @@
|
||||
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
87
README.md
@ -1,87 +0,0 @@
|
||||
# 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
1713
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -1,30 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
{
|
||||
"build": { "builder": "NIXPACKS" },
|
||||
"deploy": { "startCommand": "npm start" }
|
||||
}
|
||||
@ -1,180 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,135 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,184 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,192 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
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
210
src/client.ts
@ -1,210 +0,0 @@
|
||||
// ============================================================================
|
||||
// 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
156
src/index.ts
@ -1,156 +0,0 @@
|
||||
#!/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);
|
||||
});
|
||||
@ -1,353 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,344 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,353 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,166 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,539 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,232 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -1,213 +0,0 @@
|
||||
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
940
src/types.ts
@ -1,940 +0,0 @@
|
||||
// ============================================================================
|
||||
// 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;
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
{
|
||||
"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