skills/integration-skills/voice-ai-integration/SKILL.md
Build voice AI training platforms with Hume EVI, Twilio, and tRPC. Use when creating roleplay systems, voice call integrations, phone-based training apps, or conversational AI platforms with emotional intelligence.
npx skillsauth add abcnuts/manus-skills voice-ai-integrationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build production-ready voice AI training platforms with Hume EVI (Empathic Voice Interface), Twilio phone integration, and modern web stack (React + tRPC + Drizzle ORM).
Frontend (React + tRPC)
↓
Backend (Express + tRPC + Drizzle)
↓
Voice AI (Hume EVI) + Phone (Twilio)
↓
Database (MySQL/TiDB) - Session & conversation tracking
1. Add Hume AI Credentials
Use webdev_request_secrets to add:
HUME_API_KEY - API key from Hume dashboardHUME_SECRET_KEY - Secret key for access token generationHUME_FEMALE_CONFIG_ID - Female voice configuration UUIDHUME_MALE_CONFIG_ID - Male voice configuration UUIDHUME_WEBHOOK_URL - Webhook endpoint for call events (optional)2. Add Twilio Credentials (if phone integration needed)
TWILIO_ACCOUNT_SID - Account SID from Twilio consoleTWILIO_AUTH_TOKEN - Auth token from Twilio consoleTWILIO_PHONE_NUMBER - Purchased phone number in E.164 format (+15551234567)3. Update server environment config
Add to server/_core/env.ts:
export const ENV = {
// ... existing vars
humeApiKey: process.env.HUME_API_KEY!,
humeSecretKey: process.env.HUME_SECRET_KEY!,
humeFemaleConfigId: process.env.HUME_FEMALE_CONFIG_ID!,
humeMaleConfigId: process.env.HUME_MALE_CONFIG_ID!,
humeWebhookUrl: process.env.HUME_WEBHOOK_URL,
twilioAccountSid: process.env.TWILIO_ACCOUNT_SID,
twilioAuthToken: process.env.TWILIO_AUTH_TOKEN,
twilioPhoneNumber: process.env.TWILIO_PHONE_NUMBER,
};
Core tables needed:
Sessions table - Practice/training sessions
Conversation turns table - Message-by-message tracking
User progress table - Aggregate stats
Scripts table (if using predefined content)
Example schema (Drizzle ORM):
export const practiceSessions = mysqlTable('practice_sessions', {
id: int('id').primaryKey().autoincrement(),
userId: int('user_id').notNull(),
userCode: varchar('user_code', { length: 50 }),
archetype: varchar('archetype', { length: 50 }).notNull(),
difficulty: varchar('difficulty', { length: 50 }).notNull(),
practiceMode: varchar('practice_mode', { length: 50 }).notNull(),
voiceGender: varchar('voice_gender', { length: 10 }).notNull(),
roleplayMode: varchar('roleplay_mode', { length: 20 }).default('standard'),
callType: varchar('call_type', { length: 20 }).default('web'),
userPhoneNumber: varchar('user_phone_number', { length: 20 }),
twilioCallSid: varchar('twilio_call_sid', { length: 100 }),
status: varchar('status', { length: 20 }).notNull(),
score: int('score'),
feedback: text('feedback'),
duration: int('duration'),
createdAt: timestamp('created_at').defaultNow(),
});
Install Hume SDK:
pnpm add hume
Create server/hume-service.ts:
Key functions:
getHumeAccessToken() - Generate access token for clientgenerateSystemPrompt() - Dynamic prompt based on scenario/difficulty/modeprepareHumeSession() - Return configId + systemPromptgetConfigId() - Select male/female voice configSystem prompt pattern:
function generateSystemPrompt(params: {
archetype: string;
difficulty: string;
practiceMode: string;
roleplayMode: 'standard' | 'reverse';
scriptContent?: string;
}): string {
if (params.roleplayMode === 'reverse') {
// AI plays expert sales rep, user plays homeowner
return `You are an expert door-to-door fiber sales representative...`;
}
// Standard: AI plays homeowner, user plays sales rep
return `You are a ${params.archetype} homeowner. ${getPersonalityTraits(params.archetype)}...`;
}
Install Twilio SDK:
pnpm add twilio
Create server/twilio-service.ts:
Key functions:
getTwilioPhoneNumber() - Return configured phone numberinitiateOutboundCall() - Call user's phone number with Hume configOutbound call pattern:
export async function initiateOutboundCall(params: {
toNumber: string;
configId: string;
apiKey: string;
}): Promise<{ success: boolean; callSid?: string; error?: string }> {
const client = twilio(ENV.twilioAccountSid, ENV.twilioAuthToken);
const call = await client.calls.create({
to: params.toNumber,
from: ENV.twilioPhoneNumber,
twiml: `<Response><Connect><Stream url="wss://api.hume.ai/v0/evi/chat?config_id=${params.configId}&api_key=${params.apiKey}" /></Connect></Response>`,
});
return { success: true, callSid: call.sid };
}
Create server/practice-router.ts (or similar):
Essential endpoints:
startSession - Create session, return sessionIdgetHumeToken - Get access token for web voice callsprepareHumeSession - Get configId + systemPromptgetTwilioPhoneNumber - Return phone number for call-ininitiatePhoneCall - Trigger outbound callupdateSession - Update score/feedback/durationgetMySessions - List user's sessionsgetSession - Get single session detailsgetConversation - Get message-by-message transcriptKey pattern - session creation:
startSession: protectedProcedure
.input(z.object({
scriptId: z.number().optional(),
userCode: z.string().optional(),
archetype: z.enum(['type1', 'type2', 'type3']),
difficulty: z.enum(['easy', 'medium', 'hard']),
practiceMode: z.enum(['mode1', 'mode2']),
voiceGender: z.enum(['male', 'female']),
}))
.mutation(async ({ ctx, input }) => {
const result = await createPracticeSession({
userId: ctx.user.id,
...input,
status: 'in_progress',
});
return { sessionId: result.insertId };
})
Create client/src/hooks/useHumeVoice.ts:
Manages WebSocket connection to Hume EVI:
Pattern:
export function useHumeVoice(sessionId: number | null) {
const [status, setStatus] = useState<'idle' | 'connecting' | 'connected' | 'disconnected'>('idle');
const [messages, setMessages] = useState<Message[]>([]);
const connect = async () => {
const token = await trpc.practice.getHumeToken.query();
const config = await trpc.practice.prepareHumeSession.query({ sessionId });
// WebSocket connection to Hume EVI
const ws = new WebSocket(`wss://api.hume.ai/v0/evi/chat?access_token=${token}&config_id=${config.configId}`);
// ... handle audio streaming
};
return { status, messages, connect, disconnect };
}
Create client/src/components/VoiceCall.tsx:
Features:
Integration with useHumeVoice hook:
export function VoiceCall({ sessionId }: { sessionId: number }) {
const { status, messages, connect, disconnect } = useHumeVoice(sessionId);
return (
<Card>
<CardHeader>
<CardTitle>Practice Session</CardTitle>
<Badge>{status}</Badge>
</CardHeader>
<CardContent>
<div className="space-y-4">
{messages.map((msg, i) => (
<div key={i} className={msg.speaker === 'user' ? 'text-right' : 'text-left'}>
<p>{msg.content}</p>
</div>
))}
</div>
<Button onClick={status === 'connected' ? disconnect : connect}>
{status === 'connected' ? 'End Call' : 'Start Call'}
</Button>
</CardContent>
</Card>
);
}
Create client/src/pages/Practice.tsx:
Configuration sections:
Phone integration UI:
// Call method selection
<RadioGroup value={callMethod} onValueChange={setCallMethod}>
<RadioGroupItem value="web">Web Browser</RadioGroupItem>
<RadioGroupItem value="phone_inbound">
Call In: {twilioPhone?.phoneNumber}
</RadioGroupItem>
<RadioGroupItem value="phone_outbound">
Receive Call
</RadioGroupItem>
</RadioGroup>
// Phone number input (for outbound)
{callMethod === 'phone_outbound' && (
<Input
placeholder="+15551234567"
value={userPhoneNumber}
onChange={(e) => setUserPhoneNumber(e.target.value)}
/>
)}
Create client/src/pages/History.tsx:
Display:
Create client/src/pages/Analytics.tsx:
Aggregate stats:
1. Credential validation tests:
// server/hume.credentials.test.ts
test('Hume credentials are valid', async () => {
const token = await getHumeAccessToken();
expect(token).toBeTruthy();
});
2. Integration tests:
// server/phone-integration.test.ts
test('System prompt includes roleplay mode', () => {
const prompt = generateSystemPrompt({ roleplayMode: 'reverse', ... });
expect(prompt).toContain('expert sales representative');
});
3. End-to-end flow:
Allow users to practice by playing the "customer" role while AI demonstrates expert responses:
if (roleplayMode === 'reverse') {
systemPrompt = `You are an expert ${profession}. The user will play the role of ${customerType} and present objections or scenarios. Demonstrate how to handle them professionally...`;
} else {
systemPrompt = `You are a ${customerType}. The user is a ${profession} trying to ${goal}...`;
}
Enable managers to track individual users without requiring login:
// Auto-fill from profile or manual entry
const repCode = user?.repCode || manualRepCode || undefined;
// Store with session
await createPracticeSession({
userId: ctx.user.id,
userCode: repCode,
...otherParams
});
// Filter in manager dashboard
const sessions = await getUserSessions({ userCode: 'REP-1234' });
Adjust AI behavior based on difficulty:
const difficultyModifiers = {
rookie: 'Be friendly and receptive. Show clear buying signals.',
intermediate: 'Be neutral. Require some persuasion.',
expert: 'Be skeptical. Present common objections.',
nightmare: 'Be hostile and dismissive. Maximum difficulty.',
};
systemPrompt += ` ${difficultyModifiers[difficulty]}`;
webdev_request_secretspnpm db:push)Hume 401 errors:
HUME_API_KEY and HUME_SECRET_KEY are correctTwilio call failures:
TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKENWebSocket connection issues:
No audio in calls:
tools
Generate comprehensive demonstrations showing how to access projects and work across different environments (Manus terminals, personal computers, team collaboration). Use when users ask "how do I access this from another terminal/computer", "how do I share this with my team", "how do I get this on my Mac", or need clarification on Manus persistence vs GitHub usage.
development
Use when you have a spec or requirements for a multi-step task, before touching code
data-ai
Use when about to claim work is complete, fixed, or passing, before committing or creating PRs - requires running verification commands and confirming output before making any success claims; evidence before assertions always
development
Use when implementing any feature or bugfix, before writing implementation code