@codebakers/cli 3.3.2 → 3.3.4

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.
@@ -960,6 +960,161 @@ class CodeBakersServer {
960
960
  },
961
961
  },
962
962
  },
963
+ {
964
+ name: 'detect_intent',
965
+ description: 'CALL THIS FIRST when user request is ambiguous or could match multiple MCP tools. Analyzes user input and returns which MCP tool(s) would be appropriate, with explanations. Show the result to the user and ask for confirmation before executing. This prevents accidental destructive actions.',
966
+ inputSchema: {
967
+ type: 'object',
968
+ properties: {
969
+ userMessage: {
970
+ type: 'string',
971
+ description: 'The user\'s message or request to analyze',
972
+ },
973
+ },
974
+ required: ['userMessage'],
975
+ },
976
+ },
977
+ // VAPI Voice AI Tools
978
+ {
979
+ name: 'vapi_connect',
980
+ description: '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.',
981
+ inputSchema: {
982
+ type: 'object',
983
+ properties: {
984
+ apiKey: {
985
+ type: 'string',
986
+ description: 'VAPI API key from https://dashboard.vapi.ai',
987
+ },
988
+ },
989
+ required: ['apiKey'],
990
+ },
991
+ },
992
+ {
993
+ name: 'vapi_list_assistants',
994
+ description: '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".',
995
+ inputSchema: {
996
+ type: 'object',
997
+ properties: {
998
+ limit: {
999
+ type: 'number',
1000
+ description: 'Maximum number of assistants to return (default: 20)',
1001
+ },
1002
+ },
1003
+ },
1004
+ },
1005
+ {
1006
+ name: 'vapi_create_assistant',
1007
+ description: '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".',
1008
+ inputSchema: {
1009
+ type: 'object',
1010
+ properties: {
1011
+ name: {
1012
+ type: 'string',
1013
+ description: 'Name for the assistant',
1014
+ },
1015
+ description: {
1016
+ type: 'string',
1017
+ description: 'What the assistant should do (e.g., "Customer support for a SaaS product")',
1018
+ },
1019
+ voice: {
1020
+ type: 'string',
1021
+ enum: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'],
1022
+ description: 'Voice to use (default: alloy)',
1023
+ },
1024
+ webhookUrl: {
1025
+ type: 'string',
1026
+ description: 'Optional webhook URL for call events',
1027
+ },
1028
+ },
1029
+ required: ['name', 'description'],
1030
+ },
1031
+ },
1032
+ {
1033
+ name: 'vapi_get_assistant',
1034
+ description: 'Get details of a specific VAPI assistant including its configuration, prompts, and settings.',
1035
+ inputSchema: {
1036
+ type: 'object',
1037
+ properties: {
1038
+ assistantId: {
1039
+ type: 'string',
1040
+ description: 'The assistant ID to retrieve',
1041
+ },
1042
+ },
1043
+ required: ['assistantId'],
1044
+ },
1045
+ },
1046
+ {
1047
+ name: 'vapi_update_assistant',
1048
+ description: 'Update an existing VAPI assistant. Modify prompts, voice settings, or configurations.',
1049
+ inputSchema: {
1050
+ type: 'object',
1051
+ properties: {
1052
+ assistantId: {
1053
+ type: 'string',
1054
+ description: 'The assistant ID to update',
1055
+ },
1056
+ name: {
1057
+ type: 'string',
1058
+ description: 'New name for the assistant',
1059
+ },
1060
+ systemPrompt: {
1061
+ type: 'string',
1062
+ description: 'New system prompt for the assistant',
1063
+ },
1064
+ voice: {
1065
+ type: 'string',
1066
+ enum: ['alloy', 'echo', 'fable', 'onyx', 'nova', 'shimmer'],
1067
+ description: 'New voice to use',
1068
+ },
1069
+ },
1070
+ required: ['assistantId'],
1071
+ },
1072
+ },
1073
+ {
1074
+ name: 'vapi_get_calls',
1075
+ description: '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".',
1076
+ inputSchema: {
1077
+ type: 'object',
1078
+ properties: {
1079
+ assistantId: {
1080
+ type: 'string',
1081
+ description: 'Filter by assistant ID (optional)',
1082
+ },
1083
+ limit: {
1084
+ type: 'number',
1085
+ description: 'Maximum number of calls to return (default: 20)',
1086
+ },
1087
+ },
1088
+ },
1089
+ },
1090
+ {
1091
+ name: 'vapi_get_call',
1092
+ description: 'Get details of a specific call including full transcript, recording URL, and analysis.',
1093
+ inputSchema: {
1094
+ type: 'object',
1095
+ properties: {
1096
+ callId: {
1097
+ type: 'string',
1098
+ description: 'The call ID to retrieve',
1099
+ },
1100
+ },
1101
+ required: ['callId'],
1102
+ },
1103
+ },
1104
+ {
1105
+ name: 'vapi_generate_webhook',
1106
+ description: '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".',
1107
+ inputSchema: {
1108
+ type: 'object',
1109
+ properties: {
1110
+ events: {
1111
+ type: 'array',
1112
+ items: { type: 'string' },
1113
+ description: 'Events to handle: call-started, call-ended, speech-update, transcript, function-call, hang, tool-calls',
1114
+ },
1115
+ },
1116
+ },
1117
+ },
963
1118
  ],
964
1119
  }));
965
1120
  // Handle tool calls
@@ -1049,6 +1204,25 @@ class CodeBakersServer {
1049
1204
  return this.handleCheckUpdateNotification();
1050
1205
  case 'update_patterns':
1051
1206
  return this.handleUpdatePatterns(args);
1207
+ case 'detect_intent':
1208
+ return this.handleDetectIntent(args);
1209
+ // VAPI Voice AI handlers
1210
+ case 'vapi_connect':
1211
+ return this.handleVapiConnect(args);
1212
+ case 'vapi_list_assistants':
1213
+ return this.handleVapiListAssistants(args);
1214
+ case 'vapi_create_assistant':
1215
+ return this.handleVapiCreateAssistant(args);
1216
+ case 'vapi_get_assistant':
1217
+ return this.handleVapiGetAssistant(args);
1218
+ case 'vapi_update_assistant':
1219
+ return this.handleVapiUpdateAssistant(args);
1220
+ case 'vapi_get_calls':
1221
+ return this.handleVapiGetCalls(args);
1222
+ case 'vapi_get_call':
1223
+ return this.handleVapiGetCall(args);
1224
+ case 'vapi_generate_webhook':
1225
+ return this.handleVapiGenerateWebhook(args);
1052
1226
  default:
1053
1227
  throw new types_js_1.McpError(types_js_1.ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
1054
1228
  }
@@ -4678,6 +4852,564 @@ ${handlers.join('\n')}
4678
4852
  }],
4679
4853
  };
4680
4854
  }
4855
+ /**
4856
+ * Detect intent from user message and suggest appropriate MCP tools
4857
+ * Shows what would happen before executing, for user confirmation
4858
+ */
4859
+ handleDetectIntent(args) {
4860
+ const { userMessage } = args;
4861
+ const msg = userMessage.toLowerCase();
4862
+ // Define intent patterns with their MCP tools
4863
+ const intentPatterns = [
4864
+ {
4865
+ keywords: ['upgrade codebakers', 'update patterns', 'sync patterns', 'download patterns', 'get latest patterns'],
4866
+ tool: 'update_patterns',
4867
+ description: 'Download latest CLAUDE.md and all .claude/ modules from the CodeBakers server',
4868
+ action: 'WRITE - Will overwrite local pattern files with server versions',
4869
+ isDestructive: true,
4870
+ },
4871
+ {
4872
+ keywords: ['upgrade', 'improve code', 'make production ready', 'code quality'],
4873
+ tool: 'upgrade',
4874
+ description: 'Analyze your code for quality improvements (does NOT download patterns)',
4875
+ action: 'READ-ONLY - Analyzes code and suggests improvements',
4876
+ isDestructive: false,
4877
+ },
4878
+ {
4879
+ keywords: ['build', 'create project', 'new project', 'scaffold', 'start fresh'],
4880
+ tool: 'scaffold_project',
4881
+ description: 'Create a new project from scratch with CodeBakers patterns',
4882
+ action: 'WRITE - Will create new files and folders',
4883
+ isDestructive: true,
4884
+ },
4885
+ {
4886
+ keywords: ['add feature', 'implement', 'build feature', 'create feature'],
4887
+ tool: 'optimize_and_build',
4888
+ description: 'Optimize your feature request with AI and fetch relevant patterns',
4889
+ action: 'READ + ASSIST - Fetches patterns and guides implementation',
4890
+ isDestructive: false,
4891
+ },
4892
+ {
4893
+ keywords: ['audit', 'review code', 'check code', 'code review'],
4894
+ tool: 'run_audit',
4895
+ description: 'Run comprehensive code quality audit',
4896
+ action: 'READ-ONLY - Analyzes code and reports issues',
4897
+ isDestructive: false,
4898
+ },
4899
+ {
4900
+ keywords: ['heal', 'fix errors', 'auto-fix', 'fix bugs'],
4901
+ tool: 'heal',
4902
+ description: 'AI-powered error detection and automatic fixing',
4903
+ action: 'WRITE - May modify files to fix errors',
4904
+ isDestructive: true,
4905
+ },
4906
+ {
4907
+ keywords: ['design', 'clone design', 'copy design', 'match design'],
4908
+ tool: 'design',
4909
+ description: 'Clone a design from mockups, screenshots, or websites',
4910
+ action: 'WRITE - Will generate component files',
4911
+ isDestructive: true,
4912
+ },
4913
+ {
4914
+ keywords: ['status', 'progress', "what's built", 'where am i'],
4915
+ tool: 'project_status',
4916
+ description: 'Show current project build progress and stats',
4917
+ action: 'READ-ONLY - Shows project state',
4918
+ isDestructive: false,
4919
+ },
4920
+ {
4921
+ keywords: ['run tests', 'test', 'check tests'],
4922
+ tool: 'run_tests',
4923
+ description: 'Execute the project test suite',
4924
+ action: 'READ-ONLY - Runs tests and reports results',
4925
+ isDestructive: false,
4926
+ },
4927
+ {
4928
+ keywords: ['list patterns', 'show patterns', 'what patterns'],
4929
+ tool: 'list_patterns',
4930
+ description: 'List all available CodeBakers patterns',
4931
+ action: 'READ-ONLY - Shows available patterns',
4932
+ isDestructive: false,
4933
+ },
4934
+ {
4935
+ keywords: ['init', 'initialize', 'add patterns to existing'],
4936
+ tool: 'init_project',
4937
+ description: 'Add CodeBakers patterns to an existing project',
4938
+ action: 'WRITE - Will add CLAUDE.md and .claude/ folder',
4939
+ isDestructive: true,
4940
+ },
4941
+ ];
4942
+ // Find matching intents
4943
+ const matches = intentPatterns.filter(pattern => pattern.keywords.some(keyword => msg.includes(keyword)));
4944
+ let response = `# 🔍 Intent Detection\n\n`;
4945
+ response += `**Your message:** "${userMessage}"\n\n`;
4946
+ if (matches.length === 0) {
4947
+ response += `## No specific MCP tool detected\n\n`;
4948
+ response += `Your request doesn't clearly match any MCP tool. I'll proceed with general assistance.\n\n`;
4949
+ response += `**Available tools you might want:**\n`;
4950
+ response += `- \`update_patterns\` - Download latest patterns from server\n`;
4951
+ response += `- \`optimize_and_build\` - Get AI help building a feature\n`;
4952
+ response += `- \`run_audit\` - Review your code quality\n`;
4953
+ response += `- \`project_status\` - Check build progress\n`;
4954
+ }
4955
+ else if (matches.length === 1) {
4956
+ const match = matches[0];
4957
+ response += `## Detected Intent\n\n`;
4958
+ response += `| Property | Value |\n`;
4959
+ response += `|----------|-------|\n`;
4960
+ response += `| **Tool** | \`${match.tool}\` |\n`;
4961
+ response += `| **Description** | ${match.description} |\n`;
4962
+ response += `| **Action Type** | ${match.action} |\n`;
4963
+ response += `| **Destructive?** | ${match.isDestructive ? '⚠️ YES - modifies files' : '✅ NO - read-only'} |\n\n`;
4964
+ if (match.isDestructive) {
4965
+ response += `### ⚠️ Confirmation Required\n\n`;
4966
+ response += `This action will modify files. Do you want to proceed?\n\n`;
4967
+ response += `**Reply "yes" or "proceed" to execute, or describe what you actually want.**\n`;
4968
+ }
4969
+ else {
4970
+ response += `This is a read-only operation. Safe to proceed.\n\n`;
4971
+ response += `**Reply "yes" to execute, or clarify your request.**\n`;
4972
+ }
4973
+ }
4974
+ else {
4975
+ response += `## Multiple Possible Intents\n\n`;
4976
+ response += `Your request could match several tools:\n\n`;
4977
+ matches.forEach((match, i) => {
4978
+ response += `### Option ${i + 1}: \`${match.tool}\`\n`;
4979
+ response += `- **What it does:** ${match.description}\n`;
4980
+ response += `- **Action:** ${match.action}\n`;
4981
+ response += `- **Destructive:** ${match.isDestructive ? '⚠️ Yes' : '✅ No'}\n\n`;
4982
+ });
4983
+ response += `**Which option do you want?** Reply with the tool name or option number.\n`;
4984
+ }
4985
+ return {
4986
+ content: [{
4987
+ type: 'text',
4988
+ text: response,
4989
+ }],
4990
+ };
4991
+ }
4992
+ // ==================== VAPI Voice AI Handlers ====================
4993
+ getVapiKey() {
4994
+ // Check environment variable first
4995
+ if (process.env.VAPI_API_KEY) {
4996
+ return process.env.VAPI_API_KEY;
4997
+ }
4998
+ // Check .env file in project
4999
+ const cwd = process.cwd();
5000
+ const envPath = path.join(cwd, '.env');
5001
+ if (fs.existsSync(envPath)) {
5002
+ const envContent = fs.readFileSync(envPath, 'utf-8');
5003
+ const match = envContent.match(/VAPI_API_KEY=(.+)/);
5004
+ if (match) {
5005
+ return match[1].trim();
5006
+ }
5007
+ }
5008
+ return null;
5009
+ }
5010
+ async handleVapiConnect(args) {
5011
+ const { apiKey } = args;
5012
+ const cwd = process.cwd();
5013
+ let response = `# 🎙️ VAPI Connection Setup\n\n`;
5014
+ try {
5015
+ // Test the API key
5016
+ const testResponse = await fetch('https://api.vapi.ai/assistant', {
5017
+ method: 'GET',
5018
+ headers: {
5019
+ 'Authorization': `Bearer ${apiKey}`,
5020
+ },
5021
+ });
5022
+ if (!testResponse.ok) {
5023
+ response += `## ❌ Invalid API Key\n\n`;
5024
+ response += `The API key could not be validated. Please check:\n`;
5025
+ response += `1. Go to https://dashboard.vapi.ai\n`;
5026
+ response += `2. Navigate to Settings → API Keys\n`;
5027
+ response += `3. Copy your API key and try again\n`;
5028
+ return { content: [{ type: 'text', text: response }] };
5029
+ }
5030
+ // Save to .env file
5031
+ const envPath = path.join(cwd, '.env');
5032
+ let envContent = '';
5033
+ if (fs.existsSync(envPath)) {
5034
+ envContent = fs.readFileSync(envPath, 'utf-8');
5035
+ if (envContent.includes('VAPI_API_KEY=')) {
5036
+ envContent = envContent.replace(/VAPI_API_KEY=.*/g, `VAPI_API_KEY=${apiKey}`);
5037
+ }
5038
+ else {
5039
+ envContent += `\n# VAPI Voice AI\nVAPI_API_KEY=${apiKey}\n`;
5040
+ }
5041
+ }
5042
+ else {
5043
+ envContent = `# VAPI Voice AI\nVAPI_API_KEY=${apiKey}\n`;
5044
+ }
5045
+ fs.writeFileSync(envPath, envContent);
5046
+ response += `## ✅ VAPI Connected Successfully!\n\n`;
5047
+ response += `API key saved to \`.env\` file.\n\n`;
5048
+ response += `### Available Commands:\n`;
5049
+ response += `- \`vapi_list_assistants\` - See your assistants\n`;
5050
+ response += `- \`vapi_create_assistant\` - Create a new voice assistant\n`;
5051
+ response += `- \`vapi_get_calls\` - View call history\n`;
5052
+ response += `- \`vapi_generate_webhook\` - Add webhook handler to your project\n`;
5053
+ }
5054
+ catch (error) {
5055
+ const message = error instanceof Error ? error.message : 'Unknown error';
5056
+ response += `## ❌ Connection Failed\n\n`;
5057
+ response += `Error: ${message}\n`;
5058
+ }
5059
+ return { content: [{ type: 'text', text: response }] };
5060
+ }
5061
+ async handleVapiListAssistants(args) {
5062
+ const { limit = 20 } = args;
5063
+ const vapiKey = this.getVapiKey();
5064
+ let response = `# 🎙️ VAPI Assistants\n\n`;
5065
+ if (!vapiKey) {
5066
+ response += `## ❌ Not Connected\n\n`;
5067
+ response += `VAPI API key not found. Run \`vapi_connect\` first with your API key.\n`;
5068
+ response += `Get your key at: https://dashboard.vapi.ai\n`;
5069
+ return { content: [{ type: 'text', text: response }] };
5070
+ }
5071
+ try {
5072
+ const apiResponse = await fetch(`https://api.vapi.ai/assistant?limit=${limit}`, {
5073
+ method: 'GET',
5074
+ headers: {
5075
+ 'Authorization': `Bearer ${vapiKey}`,
5076
+ },
5077
+ });
5078
+ if (!apiResponse.ok) {
5079
+ throw new Error(`API error: ${apiResponse.status}`);
5080
+ }
5081
+ const assistants = await apiResponse.json();
5082
+ if (!assistants || assistants.length === 0) {
5083
+ response += `No assistants found.\n\n`;
5084
+ response += `Create one with \`vapi_create_assistant\`!\n`;
5085
+ }
5086
+ else {
5087
+ response += `| Name | ID | Voice | Created |\n`;
5088
+ response += `|------|-------|-------|--------|\n`;
5089
+ for (const a of assistants) {
5090
+ const created = new Date(a.createdAt).toLocaleDateString();
5091
+ const voice = a.voice?.provider || 'default';
5092
+ response += `| ${a.name || 'Unnamed'} | \`${a.id.slice(0, 8)}...\` | ${voice} | ${created} |\n`;
5093
+ }
5094
+ response += `\n**Total:** ${assistants.length} assistant(s)\n`;
5095
+ }
5096
+ }
5097
+ catch (error) {
5098
+ const message = error instanceof Error ? error.message : 'Unknown error';
5099
+ response += `## ❌ Error\n\n`;
5100
+ response += `Failed to fetch assistants: ${message}\n`;
5101
+ }
5102
+ return { content: [{ type: 'text', text: response }] };
5103
+ }
5104
+ async handleVapiCreateAssistant(args) {
5105
+ const { name, description, voice = 'alloy', webhookUrl } = args;
5106
+ const vapiKey = this.getVapiKey();
5107
+ let response = `# 🎙️ Create VAPI Assistant\n\n`;
5108
+ if (!vapiKey) {
5109
+ response += `## ❌ Not Connected\n\n`;
5110
+ response += `VAPI API key not found. Run \`vapi_connect\` first.\n`;
5111
+ return { content: [{ type: 'text', text: response }] };
5112
+ }
5113
+ try {
5114
+ // Build system prompt based on description
5115
+ const systemPrompt = `You are ${name}, a helpful voice assistant. ${description}
5116
+
5117
+ Guidelines:
5118
+ - Be conversational and natural
5119
+ - Keep responses concise (1-2 sentences when possible)
5120
+ - Ask clarifying questions if needed
5121
+ - Be friendly but professional`;
5122
+ const assistantConfig = {
5123
+ name,
5124
+ model: {
5125
+ provider: 'openai',
5126
+ model: 'gpt-4o-mini',
5127
+ systemPrompt,
5128
+ },
5129
+ voice: {
5130
+ provider: 'openai',
5131
+ voiceId: voice,
5132
+ },
5133
+ firstMessage: `Hi! I'm ${name}. How can I help you today?`,
5134
+ };
5135
+ if (webhookUrl) {
5136
+ assistantConfig.serverUrl = webhookUrl;
5137
+ }
5138
+ const apiResponse = await fetch('https://api.vapi.ai/assistant', {
5139
+ method: 'POST',
5140
+ headers: {
5141
+ 'Authorization': `Bearer ${vapiKey}`,
5142
+ 'Content-Type': 'application/json',
5143
+ },
5144
+ body: JSON.stringify(assistantConfig),
5145
+ });
5146
+ if (!apiResponse.ok) {
5147
+ const error = await apiResponse.json();
5148
+ throw new Error(error.message || `API error: ${apiResponse.status}`);
5149
+ }
5150
+ const assistant = await apiResponse.json();
5151
+ response += `## ✅ Assistant Created!\n\n`;
5152
+ response += `| Property | Value |\n`;
5153
+ response += `|----------|-------|\n`;
5154
+ response += `| **Name** | ${assistant.name} |\n`;
5155
+ response += `| **ID** | \`${assistant.id}\` |\n`;
5156
+ response += `| **Voice** | ${voice} |\n`;
5157
+ response += `| **Model** | gpt-4o-mini |\n`;
5158
+ response += `\n### Next Steps:\n`;
5159
+ response += `1. Test your assistant at https://dashboard.vapi.ai\n`;
5160
+ response += `2. Add a phone number to receive calls\n`;
5161
+ response += `3. Use \`vapi_generate_webhook\` to handle call events\n`;
5162
+ }
5163
+ catch (error) {
5164
+ const message = error instanceof Error ? error.message : 'Unknown error';
5165
+ response += `## ❌ Creation Failed\n\n`;
5166
+ response += `Error: ${message}\n`;
5167
+ }
5168
+ return { content: [{ type: 'text', text: response }] };
5169
+ }
5170
+ async handleVapiGetAssistant(args) {
5171
+ const { assistantId } = args;
5172
+ const vapiKey = this.getVapiKey();
5173
+ let response = `# 🎙️ Assistant Details\n\n`;
5174
+ if (!vapiKey) {
5175
+ response += `## ❌ Not Connected\n\nRun \`vapi_connect\` first.\n`;
5176
+ return { content: [{ type: 'text', text: response }] };
5177
+ }
5178
+ try {
5179
+ const apiResponse = await fetch(`https://api.vapi.ai/assistant/${assistantId}`, {
5180
+ headers: { 'Authorization': `Bearer ${vapiKey}` },
5181
+ });
5182
+ if (!apiResponse.ok) {
5183
+ throw new Error(`API error: ${apiResponse.status}`);
5184
+ }
5185
+ const a = await apiResponse.json();
5186
+ response += `## ${a.name || 'Unnamed Assistant'}\n\n`;
5187
+ response += `| Property | Value |\n`;
5188
+ response += `|----------|-------|\n`;
5189
+ response += `| **ID** | \`${a.id}\` |\n`;
5190
+ response += `| **Voice** | ${a.voice?.voiceId || 'default'} |\n`;
5191
+ response += `| **Model** | ${a.model?.model || 'unknown'} |\n`;
5192
+ response += `| **Created** | ${new Date(a.createdAt).toLocaleString()} |\n`;
5193
+ if (a.model?.systemPrompt) {
5194
+ response += `\n### System Prompt:\n\`\`\`\n${a.model.systemPrompt.slice(0, 500)}${a.model.systemPrompt.length > 500 ? '...' : ''}\n\`\`\`\n`;
5195
+ }
5196
+ }
5197
+ catch (error) {
5198
+ const message = error instanceof Error ? error.message : 'Unknown error';
5199
+ response += `## ❌ Error\n\n${message}\n`;
5200
+ }
5201
+ return { content: [{ type: 'text', text: response }] };
5202
+ }
5203
+ async handleVapiUpdateAssistant(args) {
5204
+ const { assistantId, name, systemPrompt, voice } = args;
5205
+ const vapiKey = this.getVapiKey();
5206
+ let response = `# 🎙️ Update Assistant\n\n`;
5207
+ if (!vapiKey) {
5208
+ response += `## ❌ Not Connected\n\nRun \`vapi_connect\` first.\n`;
5209
+ return { content: [{ type: 'text', text: response }] };
5210
+ }
5211
+ try {
5212
+ const updates = {};
5213
+ if (name)
5214
+ updates.name = name;
5215
+ if (systemPrompt)
5216
+ updates.model = { systemPrompt };
5217
+ if (voice)
5218
+ updates.voice = { provider: 'openai', voiceId: voice };
5219
+ const apiResponse = await fetch(`https://api.vapi.ai/assistant/${assistantId}`, {
5220
+ method: 'PATCH',
5221
+ headers: {
5222
+ 'Authorization': `Bearer ${vapiKey}`,
5223
+ 'Content-Type': 'application/json',
5224
+ },
5225
+ body: JSON.stringify(updates),
5226
+ });
5227
+ if (!apiResponse.ok) {
5228
+ throw new Error(`API error: ${apiResponse.status}`);
5229
+ }
5230
+ response += `## ✅ Assistant Updated!\n\n`;
5231
+ response += `Updated fields:\n`;
5232
+ if (name)
5233
+ response += `- Name: ${name}\n`;
5234
+ if (systemPrompt)
5235
+ response += `- System prompt updated\n`;
5236
+ if (voice)
5237
+ response += `- Voice: ${voice}\n`;
5238
+ }
5239
+ catch (error) {
5240
+ const message = error instanceof Error ? error.message : 'Unknown error';
5241
+ response += `## ❌ Update Failed\n\n${message}\n`;
5242
+ }
5243
+ return { content: [{ type: 'text', text: response }] };
5244
+ }
5245
+ async handleVapiGetCalls(args) {
5246
+ const { assistantId, limit = 20 } = args;
5247
+ const vapiKey = this.getVapiKey();
5248
+ let response = `# 🎙️ VAPI Call History\n\n`;
5249
+ if (!vapiKey) {
5250
+ response += `## ❌ Not Connected\n\nRun \`vapi_connect\` first.\n`;
5251
+ return { content: [{ type: 'text', text: response }] };
5252
+ }
5253
+ try {
5254
+ let url = `https://api.vapi.ai/call?limit=${limit}`;
5255
+ if (assistantId)
5256
+ url += `&assistantId=${assistantId}`;
5257
+ const apiResponse = await fetch(url, {
5258
+ headers: { 'Authorization': `Bearer ${vapiKey}` },
5259
+ });
5260
+ if (!apiResponse.ok) {
5261
+ throw new Error(`API error: ${apiResponse.status}`);
5262
+ }
5263
+ const calls = await apiResponse.json();
5264
+ if (!calls || calls.length === 0) {
5265
+ response += `No calls found.\n`;
5266
+ }
5267
+ else {
5268
+ response += `| Date | Duration | Status | Cost |\n`;
5269
+ response += `|------|----------|--------|------|\n`;
5270
+ for (const call of calls) {
5271
+ const date = new Date(call.createdAt).toLocaleString();
5272
+ const duration = call.endedAt ? Math.round((new Date(call.endedAt).getTime() - new Date(call.startedAt).getTime()) / 1000) : 0;
5273
+ const cost = call.cost ? `$${call.cost.toFixed(3)}` : '-';
5274
+ response += `| ${date} | ${duration}s | ${call.status} | ${cost} |\n`;
5275
+ }
5276
+ response += `\n**Total:** ${calls.length} call(s)\n`;
5277
+ }
5278
+ }
5279
+ catch (error) {
5280
+ const message = error instanceof Error ? error.message : 'Unknown error';
5281
+ response += `## ❌ Error\n\n${message}\n`;
5282
+ }
5283
+ return { content: [{ type: 'text', text: response }] };
5284
+ }
5285
+ async handleVapiGetCall(args) {
5286
+ const { callId } = args;
5287
+ const vapiKey = this.getVapiKey();
5288
+ let response = `# 🎙️ Call Details\n\n`;
5289
+ if (!vapiKey) {
5290
+ response += `## ❌ Not Connected\n\nRun \`vapi_connect\` first.\n`;
5291
+ return { content: [{ type: 'text', text: response }] };
5292
+ }
5293
+ try {
5294
+ const apiResponse = await fetch(`https://api.vapi.ai/call/${callId}`, {
5295
+ headers: { 'Authorization': `Bearer ${vapiKey}` },
5296
+ });
5297
+ if (!apiResponse.ok) {
5298
+ throw new Error(`API error: ${apiResponse.status}`);
5299
+ }
5300
+ const call = await apiResponse.json();
5301
+ response += `| Property | Value |\n`;
5302
+ response += `|----------|-------|\n`;
5303
+ response += `| **ID** | \`${call.id}\` |\n`;
5304
+ response += `| **Status** | ${call.status} |\n`;
5305
+ response += `| **Started** | ${new Date(call.startedAt).toLocaleString()} |\n`;
5306
+ if (call.endedAt) {
5307
+ const duration = Math.round((new Date(call.endedAt).getTime() - new Date(call.startedAt).getTime()) / 1000);
5308
+ response += `| **Duration** | ${duration} seconds |\n`;
5309
+ }
5310
+ if (call.cost)
5311
+ response += `| **Cost** | $${call.cost.toFixed(4)} |\n`;
5312
+ if (call.recordingUrl)
5313
+ response += `| **Recording** | [Listen](${call.recordingUrl}) |\n`;
5314
+ if (call.transcript) {
5315
+ response += `\n### Transcript:\n\`\`\`\n${call.transcript.slice(0, 1000)}${call.transcript.length > 1000 ? '...' : ''}\n\`\`\`\n`;
5316
+ }
5317
+ }
5318
+ catch (error) {
5319
+ const message = error instanceof Error ? error.message : 'Unknown error';
5320
+ response += `## ❌ Error\n\n${message}\n`;
5321
+ }
5322
+ return { content: [{ type: 'text', text: response }] };
5323
+ }
5324
+ handleVapiGenerateWebhook(args) {
5325
+ const { events = ['call-started', 'call-ended', 'transcript'] } = args;
5326
+ const cwd = process.cwd();
5327
+ let response = `# 🎙️ VAPI Webhook Generator\n\n`;
5328
+ // Generate webhook handler code
5329
+ const webhookCode = `import { NextRequest, NextResponse } from 'next/server';
5330
+
5331
+ // VAPI Webhook Event Types
5332
+ type VapiEventType = ${events.map(e => `'${e}'`).join(' | ')};
5333
+
5334
+ interface VapiWebhookPayload {
5335
+ type: VapiEventType;
5336
+ call?: {
5337
+ id: string;
5338
+ assistantId: string;
5339
+ phoneNumber?: string;
5340
+ customer?: {
5341
+ number: string;
5342
+ name?: string;
5343
+ };
5344
+ };
5345
+ transcript?: string;
5346
+ timestamp: string;
5347
+ }
5348
+
5349
+ export async function POST(req: NextRequest) {
5350
+ try {
5351
+ const payload: VapiWebhookPayload = await req.json();
5352
+
5353
+ console.log('[VAPI Webhook]', payload.type, payload.call?.id);
5354
+
5355
+ switch (payload.type) {
5356
+ ${events.includes('call-started') ? ` case 'call-started':
5357
+ // Handle call started
5358
+ // Example: Log to database, send notification
5359
+ console.log('Call started:', payload.call?.id);
5360
+ break;
5361
+ ` : ''}${events.includes('call-ended') ? ` case 'call-ended':
5362
+ // Handle call ended
5363
+ // Example: Save transcript, update CRM
5364
+ console.log('Call ended:', payload.call?.id);
5365
+ break;
5366
+ ` : ''}${events.includes('transcript') ? ` case 'transcript':
5367
+ // Handle real-time transcript updates
5368
+ console.log('Transcript:', payload.transcript);
5369
+ break;
5370
+ ` : ''}${events.includes('function-call') ? ` case 'function-call':
5371
+ // Handle function calls from assistant
5372
+ // Return data to be spoken by the assistant
5373
+ return NextResponse.json({ result: 'Function executed' });
5374
+ ` : ''} default:
5375
+ console.log('Unhandled event:', payload.type);
5376
+ }
5377
+
5378
+ return NextResponse.json({ received: true });
5379
+ } catch (error) {
5380
+ console.error('[VAPI Webhook Error]', error);
5381
+ return NextResponse.json({ error: 'Webhook error' }, { status: 400 });
5382
+ }
5383
+ }
5384
+ `;
5385
+ // Check if API route directory exists
5386
+ const apiDir = path.join(cwd, 'src', 'app', 'api', 'webhooks', 'vapi');
5387
+ const routePath = path.join(apiDir, 'route.ts');
5388
+ if (fs.existsSync(routePath)) {
5389
+ response += `## ⚠️ Webhook Already Exists\n\n`;
5390
+ response += `File: \`src/app/api/webhooks/vapi/route.ts\`\n\n`;
5391
+ response += `Here's the updated code if you want to replace it:\n\n`;
5392
+ }
5393
+ else {
5394
+ // Create directory and file
5395
+ fs.mkdirSync(apiDir, { recursive: true });
5396
+ fs.writeFileSync(routePath, webhookCode);
5397
+ response += `## ✅ Webhook Created!\n\n`;
5398
+ response += `File: \`src/app/api/webhooks/vapi/route.ts\`\n\n`;
5399
+ }
5400
+ response += `### Generated Code:\n\`\`\`typescript\n${webhookCode}\n\`\`\`\n\n`;
5401
+ response += `### Setup Instructions:\n`;
5402
+ response += `1. Deploy your app to get a public URL\n`;
5403
+ response += `2. Go to https://dashboard.vapi.ai → Your Assistant → Settings\n`;
5404
+ response += `3. Set webhook URL to: \`https://your-domain.com/api/webhooks/vapi\`\n`;
5405
+ response += `4. Select events: ${events.join(', ')}\n\n`;
5406
+ response += `### Handling Events:\n`;
5407
+ response += `- \`call-started\`: Triggered when a call begins\n`;
5408
+ response += `- \`call-ended\`: Triggered when a call ends (with transcript)\n`;
5409
+ response += `- \`transcript\`: Real-time transcript updates\n`;
5410
+ response += `- \`function-call\`: When assistant calls a custom function\n`;
5411
+ return { content: [{ type: 'text', text: response }] };
5412
+ }
4681
5413
  async run() {
4682
5414
  const transport = new stdio_js_1.StdioServerTransport();
4683
5415
  await this.server.connect(transport);