@codebakers/cli 3.3.3 → 3.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +17 -0
- package/dist/config.js +48 -1
- package/dist/index.js +65 -2
- package/dist/mcp/server.js +579 -0
- package/package.json +1 -1
- package/src/config.ts +52 -1
- package/src/index.ts +73 -3
- package/src/mcp/server.ts +660 -0
package/src/mcp/server.ts
CHANGED
|
@@ -1062,6 +1062,155 @@ class CodeBakersServer {
|
|
|
1062
1062
|
required: ['userMessage'],
|
|
1063
1063
|
},
|
|
1064
1064
|
},
|
|
1065
|
+
// VAPI Voice AI Tools
|
|
1066
|
+
{
|
|
1067
|
+
name: 'vapi_connect',
|
|
1068
|
+
description:
|
|
1069
|
+
'Connect to VAPI (Voice AI Platform). Sets up VAPI API credentials for voice assistant integration. Use when user says "connect vapi", "setup voice", "configure vapi", or before using other vapi_* tools.',
|
|
1070
|
+
inputSchema: {
|
|
1071
|
+
type: 'object' as const,
|
|
1072
|
+
properties: {
|
|
1073
|
+
apiKey: {
|
|
1074
|
+
type: 'string',
|
|
1075
|
+
description: 'VAPI API key from https://dashboard.vapi.ai',
|
|
1076
|
+
},
|
|
1077
|
+
},
|
|
1078
|
+
required: ['apiKey'],
|
|
1079
|
+
},
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
name: 'vapi_list_assistants',
|
|
1083
|
+
description:
|
|
1084
|
+
'List all VAPI voice assistants in your account. Shows assistant names, IDs, and configurations. Use when user asks "show my assistants", "list voice bots", or "what assistants do I have".',
|
|
1085
|
+
inputSchema: {
|
|
1086
|
+
type: 'object' as const,
|
|
1087
|
+
properties: {
|
|
1088
|
+
limit: {
|
|
1089
|
+
type: 'number',
|
|
1090
|
+
description: 'Maximum number of assistants to return (default: 20)',
|
|
1091
|
+
},
|
|
1092
|
+
},
|
|
1093
|
+
},
|
|
1094
|
+
},
|
|
1095
|
+
{
|
|
1096
|
+
name: 'vapi_create_assistant',
|
|
1097
|
+
description:
|
|
1098
|
+
'Create a new VAPI voice assistant with CodeBakers best practices. Generates assistant with proper prompts, voice settings, and webhook configurations. Use when user says "create assistant", "new voice bot", or "setup voice agent".',
|
|
1099
|
+
inputSchema: {
|
|
1100
|
+
type: 'object' as const,
|
|
1101
|
+
properties: {
|
|
1102
|
+
name: {
|
|
1103
|
+
type: 'string',
|
|
1104
|
+
description: 'Name for the assistant',
|
|
1105
|
+
},
|
|
1106
|
+
description: {
|
|
1107
|
+
type: 'string',
|
|
1108
|
+
description: 'What the assistant should do (e.g., "Customer support for a SaaS product")',
|
|
1109
|
+
},
|
|
1110
|
+
voice: {
|
|
1111
|
+
type: 'string',
|
|
1112
|
+
enum: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'],
|
|
1113
|
+
description: 'Voice to use (default: alloy)',
|
|
1114
|
+
},
|
|
1115
|
+
webhookUrl: {
|
|
1116
|
+
type: 'string',
|
|
1117
|
+
description: 'Optional webhook URL for call events',
|
|
1118
|
+
},
|
|
1119
|
+
},
|
|
1120
|
+
required: ['name', 'description'],
|
|
1121
|
+
},
|
|
1122
|
+
},
|
|
1123
|
+
{
|
|
1124
|
+
name: 'vapi_get_assistant',
|
|
1125
|
+
description:
|
|
1126
|
+
'Get details of a specific VAPI assistant including its configuration, prompts, and settings.',
|
|
1127
|
+
inputSchema: {
|
|
1128
|
+
type: 'object' as const,
|
|
1129
|
+
properties: {
|
|
1130
|
+
assistantId: {
|
|
1131
|
+
type: 'string',
|
|
1132
|
+
description: 'The assistant ID to retrieve',
|
|
1133
|
+
},
|
|
1134
|
+
},
|
|
1135
|
+
required: ['assistantId'],
|
|
1136
|
+
},
|
|
1137
|
+
},
|
|
1138
|
+
{
|
|
1139
|
+
name: 'vapi_update_assistant',
|
|
1140
|
+
description:
|
|
1141
|
+
'Update an existing VAPI assistant. Modify prompts, voice settings, or configurations.',
|
|
1142
|
+
inputSchema: {
|
|
1143
|
+
type: 'object' as const,
|
|
1144
|
+
properties: {
|
|
1145
|
+
assistantId: {
|
|
1146
|
+
type: 'string',
|
|
1147
|
+
description: 'The assistant ID to update',
|
|
1148
|
+
},
|
|
1149
|
+
name: {
|
|
1150
|
+
type: 'string',
|
|
1151
|
+
description: 'New name for the assistant',
|
|
1152
|
+
},
|
|
1153
|
+
systemPrompt: {
|
|
1154
|
+
type: 'string',
|
|
1155
|
+
description: 'New system prompt for the assistant',
|
|
1156
|
+
},
|
|
1157
|
+
voice: {
|
|
1158
|
+
type: 'string',
|
|
1159
|
+
enum: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'],
|
|
1160
|
+
description: 'New voice to use',
|
|
1161
|
+
},
|
|
1162
|
+
},
|
|
1163
|
+
required: ['assistantId'],
|
|
1164
|
+
},
|
|
1165
|
+
},
|
|
1166
|
+
{
|
|
1167
|
+
name: 'vapi_get_calls',
|
|
1168
|
+
description:
|
|
1169
|
+
'Get recent call logs from VAPI. Shows call duration, status, transcripts, and costs. Use when user asks "show calls", "call history", or "what calls happened".',
|
|
1170
|
+
inputSchema: {
|
|
1171
|
+
type: 'object' as const,
|
|
1172
|
+
properties: {
|
|
1173
|
+
assistantId: {
|
|
1174
|
+
type: 'string',
|
|
1175
|
+
description: 'Filter by assistant ID (optional)',
|
|
1176
|
+
},
|
|
1177
|
+
limit: {
|
|
1178
|
+
type: 'number',
|
|
1179
|
+
description: 'Maximum number of calls to return (default: 20)',
|
|
1180
|
+
},
|
|
1181
|
+
},
|
|
1182
|
+
},
|
|
1183
|
+
},
|
|
1184
|
+
{
|
|
1185
|
+
name: 'vapi_get_call',
|
|
1186
|
+
description:
|
|
1187
|
+
'Get details of a specific call including full transcript, recording URL, and analysis.',
|
|
1188
|
+
inputSchema: {
|
|
1189
|
+
type: 'object' as const,
|
|
1190
|
+
properties: {
|
|
1191
|
+
callId: {
|
|
1192
|
+
type: 'string',
|
|
1193
|
+
description: 'The call ID to retrieve',
|
|
1194
|
+
},
|
|
1195
|
+
},
|
|
1196
|
+
required: ['callId'],
|
|
1197
|
+
},
|
|
1198
|
+
},
|
|
1199
|
+
{
|
|
1200
|
+
name: 'vapi_generate_webhook',
|
|
1201
|
+
description:
|
|
1202
|
+
'Generate a VAPI webhook handler for your Next.js project. Creates API route with proper signature verification and event handling. Use when user says "add vapi webhook", "handle vapi events", or "setup call notifications".',
|
|
1203
|
+
inputSchema: {
|
|
1204
|
+
type: 'object' as const,
|
|
1205
|
+
properties: {
|
|
1206
|
+
events: {
|
|
1207
|
+
type: 'array',
|
|
1208
|
+
items: { type: 'string' },
|
|
1209
|
+
description: 'Events to handle: call-started, call-ended, speech-update, transcript, function-call, hang, tool-calls',
|
|
1210
|
+
},
|
|
1211
|
+
},
|
|
1212
|
+
},
|
|
1213
|
+
},
|
|
1065
1214
|
],
|
|
1066
1215
|
}));
|
|
1067
1216
|
|
|
@@ -1198,6 +1347,31 @@ class CodeBakersServer {
|
|
|
1198
1347
|
case 'detect_intent':
|
|
1199
1348
|
return this.handleDetectIntent(args as { userMessage: string });
|
|
1200
1349
|
|
|
1350
|
+
// VAPI Voice AI handlers
|
|
1351
|
+
case 'vapi_connect':
|
|
1352
|
+
return this.handleVapiConnect(args as { apiKey: string });
|
|
1353
|
+
|
|
1354
|
+
case 'vapi_list_assistants':
|
|
1355
|
+
return this.handleVapiListAssistants(args as { limit?: number });
|
|
1356
|
+
|
|
1357
|
+
case 'vapi_create_assistant':
|
|
1358
|
+
return this.handleVapiCreateAssistant(args as { name: string; description: string; voice?: string; webhookUrl?: string });
|
|
1359
|
+
|
|
1360
|
+
case 'vapi_get_assistant':
|
|
1361
|
+
return this.handleVapiGetAssistant(args as { assistantId: string });
|
|
1362
|
+
|
|
1363
|
+
case 'vapi_update_assistant':
|
|
1364
|
+
return this.handleVapiUpdateAssistant(args as { assistantId: string; name?: string; systemPrompt?: string; voice?: string });
|
|
1365
|
+
|
|
1366
|
+
case 'vapi_get_calls':
|
|
1367
|
+
return this.handleVapiGetCalls(args as { assistantId?: string; limit?: number });
|
|
1368
|
+
|
|
1369
|
+
case 'vapi_get_call':
|
|
1370
|
+
return this.handleVapiGetCall(args as { callId: string });
|
|
1371
|
+
|
|
1372
|
+
case 'vapi_generate_webhook':
|
|
1373
|
+
return this.handleVapiGenerateWebhook(args as { events?: string[] });
|
|
1374
|
+
|
|
1201
1375
|
default:
|
|
1202
1376
|
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
|
|
1203
1377
|
}
|
|
@@ -5466,6 +5640,492 @@ ${handlers.join('\n')}
|
|
|
5466
5640
|
};
|
|
5467
5641
|
}
|
|
5468
5642
|
|
|
5643
|
+
// ==================== VAPI Voice AI Handlers ====================
|
|
5644
|
+
|
|
5645
|
+
private getVapiKey(): string | null {
|
|
5646
|
+
// Check environment variable first
|
|
5647
|
+
if (process.env.VAPI_API_KEY) {
|
|
5648
|
+
return process.env.VAPI_API_KEY;
|
|
5649
|
+
}
|
|
5650
|
+
// Check .env file in project
|
|
5651
|
+
const cwd = process.cwd();
|
|
5652
|
+
const envPath = path.join(cwd, '.env');
|
|
5653
|
+
if (fs.existsSync(envPath)) {
|
|
5654
|
+
const envContent = fs.readFileSync(envPath, 'utf-8');
|
|
5655
|
+
const match = envContent.match(/VAPI_API_KEY=(.+)/);
|
|
5656
|
+
if (match) {
|
|
5657
|
+
return match[1].trim();
|
|
5658
|
+
}
|
|
5659
|
+
}
|
|
5660
|
+
return null;
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5663
|
+
private async handleVapiConnect(args: { apiKey: string }) {
|
|
5664
|
+
const { apiKey } = args;
|
|
5665
|
+
const cwd = process.cwd();
|
|
5666
|
+
|
|
5667
|
+
let response = `# 🎙️ VAPI Connection Setup\n\n`;
|
|
5668
|
+
|
|
5669
|
+
try {
|
|
5670
|
+
// Test the API key
|
|
5671
|
+
const testResponse = await fetch('https://api.vapi.ai/assistant', {
|
|
5672
|
+
method: 'GET',
|
|
5673
|
+
headers: {
|
|
5674
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
5675
|
+
},
|
|
5676
|
+
});
|
|
5677
|
+
|
|
5678
|
+
if (!testResponse.ok) {
|
|
5679
|
+
response += `## ❌ Invalid API Key\n\n`;
|
|
5680
|
+
response += `The API key could not be validated. Please check:\n`;
|
|
5681
|
+
response += `1. Go to https://dashboard.vapi.ai\n`;
|
|
5682
|
+
response += `2. Navigate to Settings → API Keys\n`;
|
|
5683
|
+
response += `3. Copy your API key and try again\n`;
|
|
5684
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5685
|
+
}
|
|
5686
|
+
|
|
5687
|
+
// Save to .env file
|
|
5688
|
+
const envPath = path.join(cwd, '.env');
|
|
5689
|
+
let envContent = '';
|
|
5690
|
+
|
|
5691
|
+
if (fs.existsSync(envPath)) {
|
|
5692
|
+
envContent = fs.readFileSync(envPath, 'utf-8');
|
|
5693
|
+
if (envContent.includes('VAPI_API_KEY=')) {
|
|
5694
|
+
envContent = envContent.replace(/VAPI_API_KEY=.*/g, `VAPI_API_KEY=${apiKey}`);
|
|
5695
|
+
} else {
|
|
5696
|
+
envContent += `\n# VAPI Voice AI\nVAPI_API_KEY=${apiKey}\n`;
|
|
5697
|
+
}
|
|
5698
|
+
} else {
|
|
5699
|
+
envContent = `# VAPI Voice AI\nVAPI_API_KEY=${apiKey}\n`;
|
|
5700
|
+
}
|
|
5701
|
+
|
|
5702
|
+
fs.writeFileSync(envPath, envContent);
|
|
5703
|
+
|
|
5704
|
+
response += `## ✅ VAPI Connected Successfully!\n\n`;
|
|
5705
|
+
response += `API key saved to \`.env\` file.\n\n`;
|
|
5706
|
+
response += `### Available Commands:\n`;
|
|
5707
|
+
response += `- \`vapi_list_assistants\` - See your assistants\n`;
|
|
5708
|
+
response += `- \`vapi_create_assistant\` - Create a new voice assistant\n`;
|
|
5709
|
+
response += `- \`vapi_get_calls\` - View call history\n`;
|
|
5710
|
+
response += `- \`vapi_generate_webhook\` - Add webhook handler to your project\n`;
|
|
5711
|
+
|
|
5712
|
+
} catch (error) {
|
|
5713
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
5714
|
+
response += `## ❌ Connection Failed\n\n`;
|
|
5715
|
+
response += `Error: ${message}\n`;
|
|
5716
|
+
}
|
|
5717
|
+
|
|
5718
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5719
|
+
}
|
|
5720
|
+
|
|
5721
|
+
private async handleVapiListAssistants(args: { limit?: number }) {
|
|
5722
|
+
const { limit = 20 } = args;
|
|
5723
|
+
const vapiKey = this.getVapiKey();
|
|
5724
|
+
|
|
5725
|
+
let response = `# 🎙️ VAPI Assistants\n\n`;
|
|
5726
|
+
|
|
5727
|
+
if (!vapiKey) {
|
|
5728
|
+
response += `## ❌ Not Connected\n\n`;
|
|
5729
|
+
response += `VAPI API key not found. Run \`vapi_connect\` first with your API key.\n`;
|
|
5730
|
+
response += `Get your key at: https://dashboard.vapi.ai\n`;
|
|
5731
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5732
|
+
}
|
|
5733
|
+
|
|
5734
|
+
try {
|
|
5735
|
+
const apiResponse = await fetch(`https://api.vapi.ai/assistant?limit=${limit}`, {
|
|
5736
|
+
method: 'GET',
|
|
5737
|
+
headers: {
|
|
5738
|
+
'Authorization': `Bearer ${vapiKey}`,
|
|
5739
|
+
},
|
|
5740
|
+
});
|
|
5741
|
+
|
|
5742
|
+
if (!apiResponse.ok) {
|
|
5743
|
+
throw new Error(`API error: ${apiResponse.status}`);
|
|
5744
|
+
}
|
|
5745
|
+
|
|
5746
|
+
const assistants = await apiResponse.json();
|
|
5747
|
+
|
|
5748
|
+
if (!assistants || assistants.length === 0) {
|
|
5749
|
+
response += `No assistants found.\n\n`;
|
|
5750
|
+
response += `Create one with \`vapi_create_assistant\`!\n`;
|
|
5751
|
+
} else {
|
|
5752
|
+
response += `| Name | ID | Voice | Created |\n`;
|
|
5753
|
+
response += `|------|-------|-------|--------|\n`;
|
|
5754
|
+
|
|
5755
|
+
for (const a of assistants) {
|
|
5756
|
+
const created = new Date(a.createdAt).toLocaleDateString();
|
|
5757
|
+
const voice = a.voice?.provider || 'default';
|
|
5758
|
+
response += `| ${a.name || 'Unnamed'} | \`${a.id.slice(0, 8)}...\` | ${voice} | ${created} |\n`;
|
|
5759
|
+
}
|
|
5760
|
+
|
|
5761
|
+
response += `\n**Total:** ${assistants.length} assistant(s)\n`;
|
|
5762
|
+
}
|
|
5763
|
+
|
|
5764
|
+
} catch (error) {
|
|
5765
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
5766
|
+
response += `## ❌ Error\n\n`;
|
|
5767
|
+
response += `Failed to fetch assistants: ${message}\n`;
|
|
5768
|
+
}
|
|
5769
|
+
|
|
5770
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5771
|
+
}
|
|
5772
|
+
|
|
5773
|
+
private async handleVapiCreateAssistant(args: { name: string; description: string; voice?: string; webhookUrl?: string }) {
|
|
5774
|
+
const { name, description, voice = 'alloy', webhookUrl } = args;
|
|
5775
|
+
const vapiKey = this.getVapiKey();
|
|
5776
|
+
|
|
5777
|
+
let response = `# 🎙️ Create VAPI Assistant\n\n`;
|
|
5778
|
+
|
|
5779
|
+
if (!vapiKey) {
|
|
5780
|
+
response += `## ❌ Not Connected\n\n`;
|
|
5781
|
+
response += `VAPI API key not found. Run \`vapi_connect\` first.\n`;
|
|
5782
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5783
|
+
}
|
|
5784
|
+
|
|
5785
|
+
try {
|
|
5786
|
+
// Build system prompt based on description
|
|
5787
|
+
const systemPrompt = `You are ${name}, a helpful voice assistant. ${description}
|
|
5788
|
+
|
|
5789
|
+
Guidelines:
|
|
5790
|
+
- Be conversational and natural
|
|
5791
|
+
- Keep responses concise (1-2 sentences when possible)
|
|
5792
|
+
- Ask clarifying questions if needed
|
|
5793
|
+
- Be friendly but professional`;
|
|
5794
|
+
|
|
5795
|
+
const assistantConfig: Record<string, unknown> = {
|
|
5796
|
+
name,
|
|
5797
|
+
model: {
|
|
5798
|
+
provider: 'openai',
|
|
5799
|
+
model: 'gpt-4o-mini',
|
|
5800
|
+
systemPrompt,
|
|
5801
|
+
},
|
|
5802
|
+
voice: {
|
|
5803
|
+
provider: 'openai',
|
|
5804
|
+
voiceId: voice,
|
|
5805
|
+
},
|
|
5806
|
+
firstMessage: `Hi! I'm ${name}. How can I help you today?`,
|
|
5807
|
+
};
|
|
5808
|
+
|
|
5809
|
+
if (webhookUrl) {
|
|
5810
|
+
assistantConfig.serverUrl = webhookUrl;
|
|
5811
|
+
}
|
|
5812
|
+
|
|
5813
|
+
const apiResponse = await fetch('https://api.vapi.ai/assistant', {
|
|
5814
|
+
method: 'POST',
|
|
5815
|
+
headers: {
|
|
5816
|
+
'Authorization': `Bearer ${vapiKey}`,
|
|
5817
|
+
'Content-Type': 'application/json',
|
|
5818
|
+
},
|
|
5819
|
+
body: JSON.stringify(assistantConfig),
|
|
5820
|
+
});
|
|
5821
|
+
|
|
5822
|
+
if (!apiResponse.ok) {
|
|
5823
|
+
const error = await apiResponse.json();
|
|
5824
|
+
throw new Error(error.message || `API error: ${apiResponse.status}`);
|
|
5825
|
+
}
|
|
5826
|
+
|
|
5827
|
+
const assistant = await apiResponse.json();
|
|
5828
|
+
|
|
5829
|
+
response += `## ✅ Assistant Created!\n\n`;
|
|
5830
|
+
response += `| Property | Value |\n`;
|
|
5831
|
+
response += `|----------|-------|\n`;
|
|
5832
|
+
response += `| **Name** | ${assistant.name} |\n`;
|
|
5833
|
+
response += `| **ID** | \`${assistant.id}\` |\n`;
|
|
5834
|
+
response += `| **Voice** | ${voice} |\n`;
|
|
5835
|
+
response += `| **Model** | gpt-4o-mini |\n`;
|
|
5836
|
+
|
|
5837
|
+
response += `\n### Next Steps:\n`;
|
|
5838
|
+
response += `1. Test your assistant at https://dashboard.vapi.ai\n`;
|
|
5839
|
+
response += `2. Add a phone number to receive calls\n`;
|
|
5840
|
+
response += `3. Use \`vapi_generate_webhook\` to handle call events\n`;
|
|
5841
|
+
|
|
5842
|
+
} catch (error) {
|
|
5843
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
5844
|
+
response += `## ❌ Creation Failed\n\n`;
|
|
5845
|
+
response += `Error: ${message}\n`;
|
|
5846
|
+
}
|
|
5847
|
+
|
|
5848
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5849
|
+
}
|
|
5850
|
+
|
|
5851
|
+
private async handleVapiGetAssistant(args: { assistantId: string }) {
|
|
5852
|
+
const { assistantId } = args;
|
|
5853
|
+
const vapiKey = this.getVapiKey();
|
|
5854
|
+
|
|
5855
|
+
let response = `# 🎙️ Assistant Details\n\n`;
|
|
5856
|
+
|
|
5857
|
+
if (!vapiKey) {
|
|
5858
|
+
response += `## ❌ Not Connected\n\nRun \`vapi_connect\` first.\n`;
|
|
5859
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5860
|
+
}
|
|
5861
|
+
|
|
5862
|
+
try {
|
|
5863
|
+
const apiResponse = await fetch(`https://api.vapi.ai/assistant/${assistantId}`, {
|
|
5864
|
+
headers: { 'Authorization': `Bearer ${vapiKey}` },
|
|
5865
|
+
});
|
|
5866
|
+
|
|
5867
|
+
if (!apiResponse.ok) {
|
|
5868
|
+
throw new Error(`API error: ${apiResponse.status}`);
|
|
5869
|
+
}
|
|
5870
|
+
|
|
5871
|
+
const a = await apiResponse.json();
|
|
5872
|
+
|
|
5873
|
+
response += `## ${a.name || 'Unnamed Assistant'}\n\n`;
|
|
5874
|
+
response += `| Property | Value |\n`;
|
|
5875
|
+
response += `|----------|-------|\n`;
|
|
5876
|
+
response += `| **ID** | \`${a.id}\` |\n`;
|
|
5877
|
+
response += `| **Voice** | ${a.voice?.voiceId || 'default'} |\n`;
|
|
5878
|
+
response += `| **Model** | ${a.model?.model || 'unknown'} |\n`;
|
|
5879
|
+
response += `| **Created** | ${new Date(a.createdAt).toLocaleString()} |\n`;
|
|
5880
|
+
|
|
5881
|
+
if (a.model?.systemPrompt) {
|
|
5882
|
+
response += `\n### System Prompt:\n\`\`\`\n${a.model.systemPrompt.slice(0, 500)}${a.model.systemPrompt.length > 500 ? '...' : ''}\n\`\`\`\n`;
|
|
5883
|
+
}
|
|
5884
|
+
|
|
5885
|
+
} catch (error) {
|
|
5886
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
5887
|
+
response += `## ❌ Error\n\n${message}\n`;
|
|
5888
|
+
}
|
|
5889
|
+
|
|
5890
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5891
|
+
}
|
|
5892
|
+
|
|
5893
|
+
private async handleVapiUpdateAssistant(args: { assistantId: string; name?: string; systemPrompt?: string; voice?: string }) {
|
|
5894
|
+
const { assistantId, name, systemPrompt, voice } = args;
|
|
5895
|
+
const vapiKey = this.getVapiKey();
|
|
5896
|
+
|
|
5897
|
+
let response = `# 🎙️ Update Assistant\n\n`;
|
|
5898
|
+
|
|
5899
|
+
if (!vapiKey) {
|
|
5900
|
+
response += `## ❌ Not Connected\n\nRun \`vapi_connect\` first.\n`;
|
|
5901
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5902
|
+
}
|
|
5903
|
+
|
|
5904
|
+
try {
|
|
5905
|
+
const updates: Record<string, unknown> = {};
|
|
5906
|
+
if (name) updates.name = name;
|
|
5907
|
+
if (systemPrompt) updates.model = { systemPrompt };
|
|
5908
|
+
if (voice) updates.voice = { provider: 'openai', voiceId: voice };
|
|
5909
|
+
|
|
5910
|
+
const apiResponse = await fetch(`https://api.vapi.ai/assistant/${assistantId}`, {
|
|
5911
|
+
method: 'PATCH',
|
|
5912
|
+
headers: {
|
|
5913
|
+
'Authorization': `Bearer ${vapiKey}`,
|
|
5914
|
+
'Content-Type': 'application/json',
|
|
5915
|
+
},
|
|
5916
|
+
body: JSON.stringify(updates),
|
|
5917
|
+
});
|
|
5918
|
+
|
|
5919
|
+
if (!apiResponse.ok) {
|
|
5920
|
+
throw new Error(`API error: ${apiResponse.status}`);
|
|
5921
|
+
}
|
|
5922
|
+
|
|
5923
|
+
response += `## ✅ Assistant Updated!\n\n`;
|
|
5924
|
+
response += `Updated fields:\n`;
|
|
5925
|
+
if (name) response += `- Name: ${name}\n`;
|
|
5926
|
+
if (systemPrompt) response += `- System prompt updated\n`;
|
|
5927
|
+
if (voice) response += `- Voice: ${voice}\n`;
|
|
5928
|
+
|
|
5929
|
+
} catch (error) {
|
|
5930
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
5931
|
+
response += `## ❌ Update Failed\n\n${message}\n`;
|
|
5932
|
+
}
|
|
5933
|
+
|
|
5934
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5935
|
+
}
|
|
5936
|
+
|
|
5937
|
+
private async handleVapiGetCalls(args: { assistantId?: string; limit?: number }) {
|
|
5938
|
+
const { assistantId, limit = 20 } = args;
|
|
5939
|
+
const vapiKey = this.getVapiKey();
|
|
5940
|
+
|
|
5941
|
+
let response = `# 🎙️ VAPI Call History\n\n`;
|
|
5942
|
+
|
|
5943
|
+
if (!vapiKey) {
|
|
5944
|
+
response += `## ❌ Not Connected\n\nRun \`vapi_connect\` first.\n`;
|
|
5945
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5946
|
+
}
|
|
5947
|
+
|
|
5948
|
+
try {
|
|
5949
|
+
let url = `https://api.vapi.ai/call?limit=${limit}`;
|
|
5950
|
+
if (assistantId) url += `&assistantId=${assistantId}`;
|
|
5951
|
+
|
|
5952
|
+
const apiResponse = await fetch(url, {
|
|
5953
|
+
headers: { 'Authorization': `Bearer ${vapiKey}` },
|
|
5954
|
+
});
|
|
5955
|
+
|
|
5956
|
+
if (!apiResponse.ok) {
|
|
5957
|
+
throw new Error(`API error: ${apiResponse.status}`);
|
|
5958
|
+
}
|
|
5959
|
+
|
|
5960
|
+
const calls = await apiResponse.json();
|
|
5961
|
+
|
|
5962
|
+
if (!calls || calls.length === 0) {
|
|
5963
|
+
response += `No calls found.\n`;
|
|
5964
|
+
} else {
|
|
5965
|
+
response += `| Date | Duration | Status | Cost |\n`;
|
|
5966
|
+
response += `|------|----------|--------|------|\n`;
|
|
5967
|
+
|
|
5968
|
+
for (const call of calls) {
|
|
5969
|
+
const date = new Date(call.createdAt).toLocaleString();
|
|
5970
|
+
const duration = call.endedAt ? Math.round((new Date(call.endedAt).getTime() - new Date(call.startedAt).getTime()) / 1000) : 0;
|
|
5971
|
+
const cost = call.cost ? `$${call.cost.toFixed(3)}` : '-';
|
|
5972
|
+
response += `| ${date} | ${duration}s | ${call.status} | ${cost} |\n`;
|
|
5973
|
+
}
|
|
5974
|
+
|
|
5975
|
+
response += `\n**Total:** ${calls.length} call(s)\n`;
|
|
5976
|
+
}
|
|
5977
|
+
|
|
5978
|
+
} catch (error) {
|
|
5979
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
5980
|
+
response += `## ❌ Error\n\n${message}\n`;
|
|
5981
|
+
}
|
|
5982
|
+
|
|
5983
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5984
|
+
}
|
|
5985
|
+
|
|
5986
|
+
private async handleVapiGetCall(args: { callId: string }) {
|
|
5987
|
+
const { callId } = args;
|
|
5988
|
+
const vapiKey = this.getVapiKey();
|
|
5989
|
+
|
|
5990
|
+
let response = `# 🎙️ Call Details\n\n`;
|
|
5991
|
+
|
|
5992
|
+
if (!vapiKey) {
|
|
5993
|
+
response += `## ❌ Not Connected\n\nRun \`vapi_connect\` first.\n`;
|
|
5994
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
5995
|
+
}
|
|
5996
|
+
|
|
5997
|
+
try {
|
|
5998
|
+
const apiResponse = await fetch(`https://api.vapi.ai/call/${callId}`, {
|
|
5999
|
+
headers: { 'Authorization': `Bearer ${vapiKey}` },
|
|
6000
|
+
});
|
|
6001
|
+
|
|
6002
|
+
if (!apiResponse.ok) {
|
|
6003
|
+
throw new Error(`API error: ${apiResponse.status}`);
|
|
6004
|
+
}
|
|
6005
|
+
|
|
6006
|
+
const call = await apiResponse.json();
|
|
6007
|
+
|
|
6008
|
+
response += `| Property | Value |\n`;
|
|
6009
|
+
response += `|----------|-------|\n`;
|
|
6010
|
+
response += `| **ID** | \`${call.id}\` |\n`;
|
|
6011
|
+
response += `| **Status** | ${call.status} |\n`;
|
|
6012
|
+
response += `| **Started** | ${new Date(call.startedAt).toLocaleString()} |\n`;
|
|
6013
|
+
if (call.endedAt) {
|
|
6014
|
+
const duration = Math.round((new Date(call.endedAt).getTime() - new Date(call.startedAt).getTime()) / 1000);
|
|
6015
|
+
response += `| **Duration** | ${duration} seconds |\n`;
|
|
6016
|
+
}
|
|
6017
|
+
if (call.cost) response += `| **Cost** | $${call.cost.toFixed(4)} |\n`;
|
|
6018
|
+
if (call.recordingUrl) response += `| **Recording** | [Listen](${call.recordingUrl}) |\n`;
|
|
6019
|
+
|
|
6020
|
+
if (call.transcript) {
|
|
6021
|
+
response += `\n### Transcript:\n\`\`\`\n${call.transcript.slice(0, 1000)}${call.transcript.length > 1000 ? '...' : ''}\n\`\`\`\n`;
|
|
6022
|
+
}
|
|
6023
|
+
|
|
6024
|
+
} catch (error) {
|
|
6025
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
6026
|
+
response += `## ❌ Error\n\n${message}\n`;
|
|
6027
|
+
}
|
|
6028
|
+
|
|
6029
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
6030
|
+
}
|
|
6031
|
+
|
|
6032
|
+
private handleVapiGenerateWebhook(args: { events?: string[] }) {
|
|
6033
|
+
const { events = ['call-started', 'call-ended', 'transcript'] } = args;
|
|
6034
|
+
const cwd = process.cwd();
|
|
6035
|
+
|
|
6036
|
+
let response = `# 🎙️ VAPI Webhook Generator\n\n`;
|
|
6037
|
+
|
|
6038
|
+
// Generate webhook handler code
|
|
6039
|
+
const webhookCode = `import { NextRequest, NextResponse } from 'next/server';
|
|
6040
|
+
|
|
6041
|
+
// VAPI Webhook Event Types
|
|
6042
|
+
type VapiEventType = ${events.map(e => `'${e}'`).join(' | ')};
|
|
6043
|
+
|
|
6044
|
+
interface VapiWebhookPayload {
|
|
6045
|
+
type: VapiEventType;
|
|
6046
|
+
call?: {
|
|
6047
|
+
id: string;
|
|
6048
|
+
assistantId: string;
|
|
6049
|
+
phoneNumber?: string;
|
|
6050
|
+
customer?: {
|
|
6051
|
+
number: string;
|
|
6052
|
+
name?: string;
|
|
6053
|
+
};
|
|
6054
|
+
};
|
|
6055
|
+
transcript?: string;
|
|
6056
|
+
timestamp: string;
|
|
6057
|
+
}
|
|
6058
|
+
|
|
6059
|
+
export async function POST(req: NextRequest) {
|
|
6060
|
+
try {
|
|
6061
|
+
const payload: VapiWebhookPayload = await req.json();
|
|
6062
|
+
|
|
6063
|
+
console.log('[VAPI Webhook]', payload.type, payload.call?.id);
|
|
6064
|
+
|
|
6065
|
+
switch (payload.type) {
|
|
6066
|
+
${events.includes('call-started') ? ` case 'call-started':
|
|
6067
|
+
// Handle call started
|
|
6068
|
+
// Example: Log to database, send notification
|
|
6069
|
+
console.log('Call started:', payload.call?.id);
|
|
6070
|
+
break;
|
|
6071
|
+
` : ''}${events.includes('call-ended') ? ` case 'call-ended':
|
|
6072
|
+
// Handle call ended
|
|
6073
|
+
// Example: Save transcript, update CRM
|
|
6074
|
+
console.log('Call ended:', payload.call?.id);
|
|
6075
|
+
break;
|
|
6076
|
+
` : ''}${events.includes('transcript') ? ` case 'transcript':
|
|
6077
|
+
// Handle real-time transcript updates
|
|
6078
|
+
console.log('Transcript:', payload.transcript);
|
|
6079
|
+
break;
|
|
6080
|
+
` : ''}${events.includes('function-call') ? ` case 'function-call':
|
|
6081
|
+
// Handle function calls from assistant
|
|
6082
|
+
// Return data to be spoken by the assistant
|
|
6083
|
+
return NextResponse.json({ result: 'Function executed' });
|
|
6084
|
+
` : ''} default:
|
|
6085
|
+
console.log('Unhandled event:', payload.type);
|
|
6086
|
+
}
|
|
6087
|
+
|
|
6088
|
+
return NextResponse.json({ received: true });
|
|
6089
|
+
} catch (error) {
|
|
6090
|
+
console.error('[VAPI Webhook Error]', error);
|
|
6091
|
+
return NextResponse.json({ error: 'Webhook error' }, { status: 400 });
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
`;
|
|
6095
|
+
|
|
6096
|
+
// Check if API route directory exists
|
|
6097
|
+
const apiDir = path.join(cwd, 'src', 'app', 'api', 'webhooks', 'vapi');
|
|
6098
|
+
const routePath = path.join(apiDir, 'route.ts');
|
|
6099
|
+
|
|
6100
|
+
if (fs.existsSync(routePath)) {
|
|
6101
|
+
response += `## ⚠️ Webhook Already Exists\n\n`;
|
|
6102
|
+
response += `File: \`src/app/api/webhooks/vapi/route.ts\`\n\n`;
|
|
6103
|
+
response += `Here's the updated code if you want to replace it:\n\n`;
|
|
6104
|
+
} else {
|
|
6105
|
+
// Create directory and file
|
|
6106
|
+
fs.mkdirSync(apiDir, { recursive: true });
|
|
6107
|
+
fs.writeFileSync(routePath, webhookCode);
|
|
6108
|
+
response += `## ✅ Webhook Created!\n\n`;
|
|
6109
|
+
response += `File: \`src/app/api/webhooks/vapi/route.ts\`\n\n`;
|
|
6110
|
+
}
|
|
6111
|
+
|
|
6112
|
+
response += `### Generated Code:\n\`\`\`typescript\n${webhookCode}\n\`\`\`\n\n`;
|
|
6113
|
+
|
|
6114
|
+
response += `### Setup Instructions:\n`;
|
|
6115
|
+
response += `1. Deploy your app to get a public URL\n`;
|
|
6116
|
+
response += `2. Go to https://dashboard.vapi.ai → Your Assistant → Settings\n`;
|
|
6117
|
+
response += `3. Set webhook URL to: \`https://your-domain.com/api/webhooks/vapi\`\n`;
|
|
6118
|
+
response += `4. Select events: ${events.join(', ')}\n\n`;
|
|
6119
|
+
|
|
6120
|
+
response += `### Handling Events:\n`;
|
|
6121
|
+
response += `- \`call-started\`: Triggered when a call begins\n`;
|
|
6122
|
+
response += `- \`call-ended\`: Triggered when a call ends (with transcript)\n`;
|
|
6123
|
+
response += `- \`transcript\`: Real-time transcript updates\n`;
|
|
6124
|
+
response += `- \`function-call\`: When assistant calls a custom function\n`;
|
|
6125
|
+
|
|
6126
|
+
return { content: [{ type: 'text' as const, text: response }] };
|
|
6127
|
+
}
|
|
6128
|
+
|
|
5469
6129
|
async run(): Promise<void> {
|
|
5470
6130
|
const transport = new StdioServerTransport();
|
|
5471
6131
|
await this.server.connect(transport);
|