qualia-framework 2.6.0 → 3.2.0

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.
Files changed (321) hide show
  1. package/CLAUDE.md +64 -0
  2. package/README.md +103 -30
  3. package/agents/builder.md +110 -0
  4. package/agents/planner.md +134 -0
  5. package/agents/qa-browser.md +186 -0
  6. package/agents/verifier.md +221 -0
  7. package/bin/cli.js +336 -531
  8. package/bin/install.js +570 -0
  9. package/bin/qualia-ui.js +299 -0
  10. package/bin/state.js +630 -0
  11. package/bin/statusline.js +252 -0
  12. package/guide.md +63 -0
  13. package/hooks/auto-update.js +139 -0
  14. package/hooks/branch-guard.js +47 -0
  15. package/hooks/migration-guard.js +60 -0
  16. package/hooks/pre-compact.js +32 -0
  17. package/hooks/pre-deploy-gate.js +110 -0
  18. package/hooks/pre-push.js +33 -0
  19. package/hooks/session-start.js +170 -0
  20. package/package.json +29 -20
  21. package/rules/design-reference.md +179 -0
  22. package/rules/frontend.md +126 -0
  23. package/skills/qualia/SKILL.md +87 -0
  24. package/skills/qualia-build/SKILL.md +97 -0
  25. package/skills/qualia-debug/SKILL.md +87 -0
  26. package/skills/qualia-design/SKILL.md +93 -0
  27. package/skills/qualia-handoff/SKILL.md +66 -0
  28. package/skills/qualia-idk/SKILL.md +8 -0
  29. package/skills/qualia-learn/SKILL.md +88 -0
  30. package/skills/qualia-new/SKILL.md +323 -0
  31. package/{framework/skills → skills}/qualia-optimize/SKILL.md +1 -1
  32. package/skills/qualia-pause/SKILL.md +63 -0
  33. package/skills/qualia-plan/SKILL.md +101 -0
  34. package/skills/qualia-polish/SKILL.md +157 -0
  35. package/skills/qualia-quick/SKILL.md +37 -0
  36. package/skills/qualia-report/SKILL.md +105 -0
  37. package/skills/qualia-resume/SKILL.md +49 -0
  38. package/skills/qualia-review/SKILL.md +76 -0
  39. package/skills/qualia-ship/SKILL.md +90 -0
  40. package/skills/qualia-skill-new/SKILL.md +167 -0
  41. package/skills/qualia-task/SKILL.md +91 -0
  42. package/skills/qualia-verify/SKILL.md +113 -0
  43. package/templates/DESIGN.md +137 -0
  44. package/templates/plan.md +28 -0
  45. package/templates/project.md +22 -0
  46. package/templates/state.md +27 -0
  47. package/templates/tracking.json +20 -0
  48. package/tests/bin.test.sh +673 -0
  49. package/tests/hooks.test.sh +315 -0
  50. package/tests/state.test.sh +535 -0
  51. package/tests/statusline.test.sh +243 -0
  52. package/bin/collect-metrics.sh +0 -62
  53. package/framework/.claudeignore +0 -51
  54. package/framework/CLAUDE.md +0 -51
  55. package/framework/MCP_SETUP.md +0 -229
  56. package/framework/agents/architecture-strategist.md +0 -53
  57. package/framework/agents/backend-agent.md +0 -150
  58. package/framework/agents/code-simplicity-reviewer.md +0 -86
  59. package/framework/agents/frontend-agent.md +0 -111
  60. package/framework/agents/kieran-typescript-reviewer.md +0 -96
  61. package/framework/agents/performance-oracle.md +0 -111
  62. package/framework/agents/qualia-codebase-mapper.md +0 -761
  63. package/framework/agents/qualia-debugger.md +0 -1204
  64. package/framework/agents/qualia-executor.md +0 -882
  65. package/framework/agents/qualia-integration-checker.md +0 -424
  66. package/framework/agents/qualia-phase-researcher.md +0 -457
  67. package/framework/agents/qualia-plan-checker.md +0 -700
  68. package/framework/agents/qualia-planner.md +0 -1245
  69. package/framework/agents/qualia-project-researcher.md +0 -603
  70. package/framework/agents/qualia-research-synthesizer.md +0 -200
  71. package/framework/agents/qualia-roadmapper.md +0 -606
  72. package/framework/agents/qualia-verifier.md +0 -686
  73. package/framework/agents/red-team-qa.md +0 -130
  74. package/framework/agents/security-auditor.md +0 -72
  75. package/framework/agents/team-orchestrator.md +0 -229
  76. package/framework/agents/teams/framework-audit-team.md +0 -66
  77. package/framework/agents/teams/full-stack-team.md +0 -48
  78. package/framework/agents/teams/optimize-team.md +0 -53
  79. package/framework/agents/teams/review-team.md +0 -70
  80. package/framework/agents/teams/ship-team.md +0 -86
  81. package/framework/agents/test-agent.md +0 -182
  82. package/framework/hooks/auto-format.sh +0 -54
  83. package/framework/hooks/block-env-edit.sh +0 -42
  84. package/framework/hooks/branch-guard.sh +0 -43
  85. package/framework/hooks/confirm-delete.sh +0 -59
  86. package/framework/hooks/migration-validate.sh +0 -77
  87. package/framework/hooks/notification-speak.sh +0 -16
  88. package/framework/hooks/pre-commit.sh +0 -100
  89. package/framework/hooks/pre-compact.sh +0 -56
  90. package/framework/hooks/pre-deploy-gate.sh +0 -160
  91. package/framework/hooks/qualia-colors.sh +0 -32
  92. package/framework/hooks/retention-cleanup.sh +0 -62
  93. package/framework/hooks/save-session-state.sh +0 -185
  94. package/framework/hooks/session-context-loader.sh +0 -96
  95. package/framework/hooks/session-learn.sh +0 -32
  96. package/framework/hooks/skill-announce.sh +0 -123
  97. package/framework/hooks/tool-error-announce.sh +0 -27
  98. package/framework/install.ps1 +0 -323
  99. package/framework/install.sh +0 -313
  100. package/framework/qualia-framework/VERSION +0 -1
  101. package/framework/qualia-framework/assets/qualia-logo.png +0 -0
  102. package/framework/qualia-framework/bin/collect-metrics.sh +0 -67
  103. package/framework/qualia-framework/bin/generate-report-docx.py +0 -429
  104. package/framework/qualia-framework/bin/qualia-tools.js +0 -2201
  105. package/framework/qualia-framework/bin/qualia-tools.test.js +0 -1054
  106. package/framework/qualia-framework/references/checkpoints.md +0 -775
  107. package/framework/qualia-framework/references/completion-checklists.md +0 -359
  108. package/framework/qualia-framework/references/continuation-format.md +0 -249
  109. package/framework/qualia-framework/references/continuation-prompt.md +0 -97
  110. package/framework/qualia-framework/references/decimal-phase-calculation.md +0 -65
  111. package/framework/qualia-framework/references/design-quality.md +0 -56
  112. package/framework/qualia-framework/references/employee-guide.md +0 -167
  113. package/framework/qualia-framework/references/git-integration.md +0 -254
  114. package/framework/qualia-framework/references/git-planning-commit.md +0 -50
  115. package/framework/qualia-framework/references/model-profile-resolution.md +0 -32
  116. package/framework/qualia-framework/references/model-profiles.md +0 -73
  117. package/framework/qualia-framework/references/phase-argument-parsing.md +0 -61
  118. package/framework/qualia-framework/references/planning-config.md +0 -195
  119. package/framework/qualia-framework/references/questioning.md +0 -141
  120. package/framework/qualia-framework/references/tdd.md +0 -263
  121. package/framework/qualia-framework/references/ui-brand.md +0 -160
  122. package/framework/qualia-framework/references/verification-patterns.md +0 -612
  123. package/framework/qualia-framework/templates/DEBUG.md +0 -159
  124. package/framework/qualia-framework/templates/DESIGN.md +0 -81
  125. package/framework/qualia-framework/templates/UAT.md +0 -247
  126. package/framework/qualia-framework/templates/codebase/architecture.md +0 -255
  127. package/framework/qualia-framework/templates/codebase/concerns.md +0 -310
  128. package/framework/qualia-framework/templates/codebase/conventions.md +0 -307
  129. package/framework/qualia-framework/templates/codebase/integrations.md +0 -280
  130. package/framework/qualia-framework/templates/codebase/stack.md +0 -186
  131. package/framework/qualia-framework/templates/codebase/structure.md +0 -285
  132. package/framework/qualia-framework/templates/codebase/testing.md +0 -480
  133. package/framework/qualia-framework/templates/config.json +0 -35
  134. package/framework/qualia-framework/templates/context.md +0 -283
  135. package/framework/qualia-framework/templates/continue-here.md +0 -78
  136. package/framework/qualia-framework/templates/debug-subagent-prompt.md +0 -91
  137. package/framework/qualia-framework/templates/discovery.md +0 -146
  138. package/framework/qualia-framework/templates/lab-notes.md +0 -16
  139. package/framework/qualia-framework/templates/milestone-archive.md +0 -123
  140. package/framework/qualia-framework/templates/milestone.md +0 -115
  141. package/framework/qualia-framework/templates/phase-prompt.md +0 -567
  142. package/framework/qualia-framework/templates/planner-subagent-prompt.md +0 -117
  143. package/framework/qualia-framework/templates/project.md +0 -184
  144. package/framework/qualia-framework/templates/projects/ai-agent.md +0 -156
  145. package/framework/qualia-framework/templates/projects/mobile-app.md +0 -181
  146. package/framework/qualia-framework/templates/projects/voice-agent.md +0 -134
  147. package/framework/qualia-framework/templates/projects/website.md +0 -137
  148. package/framework/qualia-framework/templates/requirements.md +0 -231
  149. package/framework/qualia-framework/templates/research-project/ARCHITECTURE.md +0 -204
  150. package/framework/qualia-framework/templates/research-project/FEATURES.md +0 -147
  151. package/framework/qualia-framework/templates/research-project/PITFALLS.md +0 -200
  152. package/framework/qualia-framework/templates/research-project/STACK.md +0 -120
  153. package/framework/qualia-framework/templates/research-project/SUMMARY.md +0 -170
  154. package/framework/qualia-framework/templates/research.md +0 -552
  155. package/framework/qualia-framework/templates/roadmap.md +0 -206
  156. package/framework/qualia-framework/templates/state.md +0 -179
  157. package/framework/qualia-framework/templates/summary-complex.md +0 -59
  158. package/framework/qualia-framework/templates/summary-minimal.md +0 -41
  159. package/framework/qualia-framework/templates/summary-standard.md +0 -48
  160. package/framework/qualia-framework/templates/summary.md +0 -246
  161. package/framework/qualia-framework/templates/user-setup.md +0 -311
  162. package/framework/qualia-framework/templates/verification-report.md +0 -322
  163. package/framework/qualia-framework/workflows/add-phase.md +0 -179
  164. package/framework/qualia-framework/workflows/add-todo.md +0 -157
  165. package/framework/qualia-framework/workflows/audit-milestone.md +0 -241
  166. package/framework/qualia-framework/workflows/check-todos.md +0 -176
  167. package/framework/qualia-framework/workflows/complete-milestone.md +0 -858
  168. package/framework/qualia-framework/workflows/diagnose-issues.md +0 -219
  169. package/framework/qualia-framework/workflows/discovery-phase.md +0 -289
  170. package/framework/qualia-framework/workflows/discuss-phase.md +0 -534
  171. package/framework/qualia-framework/workflows/execute-phase.md +0 -559
  172. package/framework/qualia-framework/workflows/execute-plan.md +0 -438
  173. package/framework/qualia-framework/workflows/help.md +0 -470
  174. package/framework/qualia-framework/workflows/insert-phase.md +0 -220
  175. package/framework/qualia-framework/workflows/list-phase-assumptions.md +0 -178
  176. package/framework/qualia-framework/workflows/map-codebase.md +0 -327
  177. package/framework/qualia-framework/workflows/new-milestone.md +0 -363
  178. package/framework/qualia-framework/workflows/new-project.md +0 -982
  179. package/framework/qualia-framework/workflows/pause-work.md +0 -122
  180. package/framework/qualia-framework/workflows/plan-milestone-gaps.md +0 -256
  181. package/framework/qualia-framework/workflows/plan-phase.md +0 -422
  182. package/framework/qualia-framework/workflows/progress.md +0 -389
  183. package/framework/qualia-framework/workflows/quick.md +0 -252
  184. package/framework/qualia-framework/workflows/remove-phase.md +0 -326
  185. package/framework/qualia-framework/workflows/research-phase.md +0 -74
  186. package/framework/qualia-framework/workflows/resume-project.md +0 -306
  187. package/framework/qualia-framework/workflows/set-profile.md +0 -80
  188. package/framework/qualia-framework/workflows/settings.md +0 -145
  189. package/framework/qualia-framework/workflows/transition.md +0 -556
  190. package/framework/qualia-framework/workflows/update.md +0 -197
  191. package/framework/qualia-framework/workflows/verify-phase.md +0 -195
  192. package/framework/qualia-framework/workflows/verify-work.md +0 -625
  193. package/framework/rules/context7.md +0 -14
  194. package/framework/rules/frontend.md +0 -33
  195. package/framework/rules/speed.md +0 -23
  196. package/framework/scripts/__pycache__/say.cpython-314.pyc +0 -0
  197. package/framework/scripts/apply-retention.sh +0 -120
  198. package/framework/scripts/bootstrap-pop-os.sh +0 -354
  199. package/framework/scripts/claude-voice +0 -13
  200. package/framework/scripts/cleanup.sh +0 -131
  201. package/framework/scripts/cowork-mode.sh +0 -141
  202. package/framework/scripts/generate-project-claude-md.sh +0 -153
  203. package/framework/scripts/load-test-webhook.js +0 -172
  204. package/framework/scripts/say.py +0 -236
  205. package/framework/scripts/showcase-video-recorder/ffmpeg-builder.js +0 -167
  206. package/framework/scripts/showcase-video-recorder/playwright-helpers.js +0 -216
  207. package/framework/scripts/speak.py +0 -55
  208. package/framework/scripts/speak.sh +0 -18
  209. package/framework/scripts/status.sh +0 -138
  210. package/framework/scripts/sync-to-framework.sh +0 -65
  211. package/framework/scripts/voice-hotkey.py +0 -227
  212. package/framework/scripts/voice-input.sh +0 -51
  213. package/framework/skills/animate/SKILL.md +0 -202
  214. package/framework/skills/bolder/SKILL.md +0 -144
  215. package/framework/skills/browser-qa/SKILL.md +0 -536
  216. package/framework/skills/clarify/SKILL.md +0 -179
  217. package/framework/skills/client-handoff/SKILL.md +0 -135
  218. package/framework/skills/collab-onboard/SKILL.md +0 -111
  219. package/framework/skills/colorize/SKILL.md +0 -170
  220. package/framework/skills/critique/SKILL.md +0 -126
  221. package/framework/skills/deep-research/SKILL.md +0 -240
  222. package/framework/skills/delight/SKILL.md +0 -329
  223. package/framework/skills/deploy/SKILL.md +0 -261
  224. package/framework/skills/deploy-verify/SKILL.md +0 -377
  225. package/framework/skills/deploy-verify/scripts/canary-check.sh +0 -206
  226. package/framework/skills/deploy-verify/scripts/check-console-errors.js +0 -147
  227. package/framework/skills/deploy-verify/scripts/check-cwv.js +0 -139
  228. package/framework/skills/deploy-verify/scripts/project-detect.sh +0 -84
  229. package/framework/skills/deploy-verify/scripts/verify.sh +0 -548
  230. package/framework/skills/design-quieter/SKILL.md +0 -130
  231. package/framework/skills/distill/SKILL.md +0 -149
  232. package/framework/skills/docs-lookup/SKILL.md +0 -79
  233. package/framework/skills/fcm-notifications/SKILL.md +0 -125
  234. package/framework/skills/financial-ledger/SKILL.md +0 -1039
  235. package/framework/skills/frontend-master/NOTICE.md +0 -4
  236. package/framework/skills/frontend-master/SKILL.md +0 -127
  237. package/framework/skills/frontend-master/reference/color-and-contrast.md +0 -132
  238. package/framework/skills/frontend-master/reference/interaction-design.md +0 -123
  239. package/framework/skills/frontend-master/reference/motion-design.md +0 -99
  240. package/framework/skills/frontend-master/reference/responsive-design.md +0 -114
  241. package/framework/skills/frontend-master/reference/spatial-design.md +0 -100
  242. package/framework/skills/frontend-master/reference/typography.md +0 -131
  243. package/framework/skills/frontend-master/reference/ux-writing.md +0 -107
  244. package/framework/skills/harden/SKILL.md +0 -357
  245. package/framework/skills/i18n-rtl/SKILL.md +0 -752
  246. package/framework/skills/learn/SKILL.md +0 -95
  247. package/framework/skills/memory/SKILL.md +0 -50
  248. package/framework/skills/mobile-expo/SKILL.md +0 -977
  249. package/framework/skills/mobile-expo/references/store-checklist.md +0 -550
  250. package/framework/skills/nestjs-backend/README.md +0 -73
  251. package/framework/skills/nestjs-backend/SKILL.md +0 -446
  252. package/framework/skills/nestjs-backend/references/templates.md +0 -1173
  253. package/framework/skills/normalize/SKILL.md +0 -79
  254. package/framework/skills/onboard/SKILL.md +0 -242
  255. package/framework/skills/openrouter-agent/SKILL.md +0 -922
  256. package/framework/skills/polish/SKILL.md +0 -209
  257. package/framework/skills/pr/SKILL.md +0 -66
  258. package/framework/skills/qualia/SKILL.md +0 -199
  259. package/framework/skills/qualia-add-todo/SKILL.md +0 -68
  260. package/framework/skills/qualia-audit-milestone/SKILL.md +0 -95
  261. package/framework/skills/qualia-check-todos/SKILL.md +0 -55
  262. package/framework/skills/qualia-complete-milestone/SKILL.md +0 -134
  263. package/framework/skills/qualia-debug/SKILL.md +0 -149
  264. package/framework/skills/qualia-design/SKILL.md +0 -203
  265. package/framework/skills/qualia-discuss-phase/SKILL.md +0 -72
  266. package/framework/skills/qualia-evolve/SKILL.md +0 -200
  267. package/framework/skills/qualia-execute-phase/SKILL.md +0 -89
  268. package/framework/skills/qualia-framework-audit/SKILL.md +0 -604
  269. package/framework/skills/qualia-guide/SKILL.md +0 -32
  270. package/framework/skills/qualia-help/SKILL.md +0 -114
  271. package/framework/skills/qualia-idk/SKILL.md +0 -352
  272. package/framework/skills/qualia-list-phase-assumptions/SKILL.md +0 -67
  273. package/framework/skills/qualia-new-milestone/SKILL.md +0 -72
  274. package/framework/skills/qualia-new-project/SKILL.md +0 -232
  275. package/framework/skills/qualia-pause-work/SKILL.md +0 -96
  276. package/framework/skills/qualia-plan-milestone-gaps/SKILL.md +0 -57
  277. package/framework/skills/qualia-plan-phase/SKILL.md +0 -104
  278. package/framework/skills/qualia-production-check/SKILL.md +0 -0
  279. package/framework/skills/qualia-progress/SKILL.md +0 -53
  280. package/framework/skills/qualia-quick/SKILL.md +0 -89
  281. package/framework/skills/qualia-report/SKILL.md +0 -166
  282. package/framework/skills/qualia-research-phase/SKILL.md +0 -88
  283. package/framework/skills/qualia-resume-work/SKILL.md +0 -62
  284. package/framework/skills/qualia-review/SKILL.md +0 -263
  285. package/framework/skills/qualia-start/SKILL.md +0 -161
  286. package/framework/skills/qualia-verify-work/SKILL.md +0 -132
  287. package/framework/skills/rag/SKILL.md +0 -750
  288. package/framework/skills/responsive/SKILL.md +0 -231
  289. package/framework/skills/retro/SKILL.md +0 -284
  290. package/framework/skills/sakani-conventions/SKILL.md +0 -136
  291. package/framework/skills/sakani-conventions/evals/evals.json +0 -23
  292. package/framework/skills/sakani-conventions/references/entities.md +0 -365
  293. package/framework/skills/sakani-conventions/references/error-codes.md +0 -95
  294. package/framework/skills/seo-master/SKILL.md +0 -490
  295. package/framework/skills/seo-master/references/checklist.md +0 -199
  296. package/framework/skills/seo-master/references/structured-data.md +0 -609
  297. package/framework/skills/ship/SKILL.md +0 -239
  298. package/framework/skills/stack-researcher/SKILL.md +0 -215
  299. package/framework/skills/status/SKILL.md +0 -154
  300. package/framework/skills/status/scripts/health-check.sh +0 -562
  301. package/framework/skills/subscription-payments/SKILL.md +0 -250
  302. package/framework/skills/supabase/SKILL.md +0 -973
  303. package/framework/skills/supabase/references/templates.md +0 -159
  304. package/framework/skills/team/SKILL.md +0 -67
  305. package/framework/skills/test-runner/SKILL.md +0 -202
  306. package/framework/skills/voice-agent/SKILL.md +0 -1312
  307. package/framework/skills/zoho-workflow/SKILL.md +0 -51
  308. package/framework/statusline-command.sh +0 -117
  309. package/framework/teams/default/inboxes/plan-04.json +0 -9
  310. package/framework/teams/review-team.md +0 -75
  311. package/framework/teams/ship-team.md +0 -86
  312. package/profiles/fawzi.json +0 -16
  313. package/profiles/hasan.json +0 -16
  314. package/profiles/moayad.json +0 -16
  315. package/templates/CLAUDE-owner.md +0 -52
  316. package/templates/CLAUDE.md.hbs +0 -58
  317. package/templates/env.claude.template +0 -12
  318. package/templates/settings.json +0 -172
  319. package/uninstall.sh +0 -90
  320. /package/{framework/rules → rules}/deployment.md +0 -0
  321. /package/{framework/rules → rules}/security.md +0 -0
@@ -1,1312 +0,0 @@
1
- ---
2
- name: voice-agent
3
- description: "Build complete voice AI agents with Retell AI + ElevenLabs — design conversational flow, create Retell agent, integrate Supabase backend, configure webhooks, deploy and test. Use this skill whenever the user says 'build voice agent', 'create voice assistant', 'voice AI', 'phone bot', 'retell agent', or wants to build any voice-based AI system. Also trigger on 'retell', 'elevenlabs', 'voice webhook', 'call agent'."
4
- tags: [voice, retell, elevenlabs, ai-agent, supabase]
5
- ---
6
-
7
- # Voice Agent Builder
8
-
9
- Build complete voice AI agents from design to deployment using Retell AI (call orchestration) + ElevenLabs (TTS/voice cloning) + Supabase (backend).
10
-
11
- **Announce at start:** "Activating voice agent builder. Let me set up your Retell AI + ElevenLabs agent."
12
-
13
- ## Pipeline
14
-
15
- ```
16
- 1. Design Conversation Flow
17
- |
18
- 2. Create Retell Agent
19
- |
20
- 3. Setup Supabase Backend
21
- |
22
- 4. Configure Webhooks
23
- |
24
- 5. Deploy & Test
25
- ```
26
-
27
- ## Quick Start
28
-
29
- Say: "build voice agent for [use case]"
30
-
31
- Example: "build voice agent for appointment scheduling"
32
-
33
- I will:
34
- 1. Design the conversation flow (state machine + flow diagram)
35
- 2. Create Retell agent with ElevenLabs voice
36
- 3. Set up Supabase tables (call_logs, agent_state, domain tables)
37
- 4. Build webhook handler as Supabase Edge Function
38
- 5. Assign phone number and test
39
-
40
- ## Environment Variables Needed
41
-
42
- ```env
43
- # Retell AI
44
- RETELL_API_KEY=key_...
45
-
46
- # ElevenLabs
47
- ELEVENLABS_API_KEY=xi_...
48
-
49
- # Supabase (auto-available in Edge Functions)
50
- SUPABASE_URL=https://xxx.supabase.co
51
- SUPABASE_SERVICE_ROLE_KEY=eyJ...
52
-
53
- # LLM (if using OpenRouter for custom LLM)
54
- OPENROUTER_API_KEY=sk-or-...
55
- ```
56
-
57
- ---
58
-
59
- ## Stage 1: Design Conversation Flow
60
-
61
- ### Flow Diagram
62
-
63
- ```
64
- +--------------------+
65
- | Call Starts |
66
- +--------+-----------+
67
- |
68
- +--------v-----------+
69
- | Greeting |
70
- | "Hello, thanks for |
71
- | calling. How can |
72
- | I help you?" |
73
- +--------+-----------+
74
- |
75
- +--------v-----------+
76
- | Intent Detection |
77
- | - Book appointment |
78
- | - Check status |
79
- | - General inquiry |
80
- | - Transfer to human |
81
- +--------+-----------+
82
- |
83
- +-----+------+------+
84
- | | |
85
- +--v---+ +----v--+ +-v--------+
86
- | Book | | Check | | Transfer |
87
- +--+---+ +----+--+ +----------+
88
- | |
89
- +--v-----------v--+
90
- | Confirm & |
91
- | End Call |
92
- +-----------------+
93
- ```
94
-
95
- ### State Machine
96
-
97
- ```typescript
98
- type ConversationState =
99
- | 'GREETING'
100
- | 'INTENT_DETECTION'
101
- | 'BOOKING'
102
- | 'COLLECT_DATE'
103
- | 'COLLECT_TIME'
104
- | 'COLLECT_NAME'
105
- | 'CONFIRM_BOOKING'
106
- | 'STATUS_CHECK'
107
- | 'GENERAL_INQUIRY'
108
- | 'TRANSFER'
109
- | 'FAREWELL';
110
-
111
- const conversationFlow: Record<ConversationState, {
112
- next: ConversationState[];
113
- actions: string[];
114
- }> = {
115
- GREETING: {
116
- next: ['INTENT_DETECTION'],
117
- actions: ['playGreeting'],
118
- },
119
- INTENT_DETECTION: {
120
- next: ['BOOKING', 'STATUS_CHECK', 'GENERAL_INQUIRY', 'TRANSFER'],
121
- actions: ['detectIntent'],
122
- },
123
- BOOKING: {
124
- next: ['COLLECT_DATE'],
125
- actions: ['startBookingFlow'],
126
- },
127
- COLLECT_DATE: {
128
- next: ['COLLECT_TIME'],
129
- actions: ['askForDate', 'validateDate'],
130
- },
131
- COLLECT_TIME: {
132
- next: ['COLLECT_NAME'],
133
- actions: ['askForTime', 'checkAvailability'],
134
- },
135
- COLLECT_NAME: {
136
- next: ['CONFIRM_BOOKING'],
137
- actions: ['askForName'],
138
- },
139
- CONFIRM_BOOKING: {
140
- next: ['FAREWELL', 'COLLECT_DATE'], // retry if rejected
141
- actions: ['readBackDetails', 'bookAppointment'],
142
- },
143
- STATUS_CHECK: {
144
- next: ['FAREWELL', 'INTENT_DETECTION'],
145
- actions: ['lookupAppointment', 'readStatus'],
146
- },
147
- GENERAL_INQUIRY: {
148
- next: ['FAREWELL', 'INTENT_DETECTION'],
149
- actions: ['answerQuestion'],
150
- },
151
- TRANSFER: {
152
- next: ['FAREWELL'],
153
- actions: ['transferToHuman'],
154
- },
155
- FAREWELL: {
156
- next: [],
157
- actions: ['endCall'],
158
- },
159
- };
160
- ```
161
-
162
- ### System Prompt Template
163
-
164
- ```
165
- You are a friendly and professional [ROLE] for [COMPANY].
166
- Your job is to help callers [PRIMARY_TASK].
167
-
168
- ## Capabilities
169
- - [Capability 1]
170
- - [Capability 2]
171
- - [Capability 3]
172
-
173
- ## Conversation Rules
174
- 1. Keep responses to 1-2 sentences. Callers hate long monologues.
175
- 2. Always confirm critical info back to the caller before acting.
176
- 3. If unsure, ask a clarifying question rather than guessing.
177
- 4. Never make up information. If you don't know, say so.
178
- 5. Be warm but efficient — respect the caller's time.
179
- 6. If the caller wants a human, transfer immediately. Don't argue.
180
-
181
- ## Available Tools
182
- - book_appointment: Book a new appointment
183
- - check_availability: Check open time slots
184
- - lookup_appointment: Find existing appointment by phone
185
-
186
- ## Escalation
187
- If the caller is frustrated, angry, or asks for a manager:
188
- → Apologize briefly, then transfer to a human operator.
189
- ```
190
-
191
- ---
192
-
193
- ## Stage 2: Create Retell Agent
194
-
195
- ### Agent Configuration
196
-
197
- ```typescript
198
- interface RetellAgentConfig {
199
- agent_name: string;
200
- voice_id: string; // ElevenLabs voice ID
201
- response_engine: {
202
- type: 'retell-llm';
203
- llm_id: string; // Created separately via Retell LLM API
204
- } | {
205
- type: 'custom-llm';
206
- llm_websocket_url: string; // Your own LLM WebSocket endpoint
207
- };
208
- language: string; // 'en-US', 'ar', 'el', etc.
209
- voice_temperature: number; // 0-2, controls voice expressiveness
210
- voice_speed: number; // 0.5-2, speech rate
211
- responsiveness: number; // 0-1, how quickly agent responds
212
- interruption_sensitivity: number; // 0-1, how easily caller can interrupt
213
- enable_backchannel: boolean; // "uh huh", "I see" filler words
214
- backchannel_frequency: number; // 0-1
215
- reminder_trigger_ms: number; // Silence before nudge (default 10000)
216
- reminder_max_count: number; // Max nudges before ending (default 2)
217
- ambient_sound: string | null; // 'coffee-shop', 'convention-hall', 'summer-outdoor', 'mountain-spring', 'static-noise', 'call-center' or null
218
- begin_message: string | null; // First message (null = wait for caller)
219
- webhook_url: string; // Your webhook endpoint
220
- post_call_analysis_data: Array<{
221
- type: 'string' | 'enum' | 'boolean';
222
- name: string;
223
- description: string;
224
- examples?: string[];
225
- options?: string[];
226
- }>;
227
- }
228
- ```
229
-
230
- ### Create Agent via Retell API
231
-
232
- ```typescript
233
- // Create the Retell LLM first (defines the brain)
234
- const llmResponse = await fetch('https://api.retellai.com/v2/create-retell-llm', {
235
- method: 'POST',
236
- headers: {
237
- 'Authorization': `Bearer ${RETELL_API_KEY}`,
238
- 'Content-Type': 'application/json',
239
- },
240
- body: JSON.stringify({
241
- model: 'claude-3-5-sonnet', // Or use 'gpt-4o', 'claude-3-5-haiku'
242
- general_prompt: `You are a friendly appointment scheduling assistant for Qualia Solutions.
243
- Your job is to help callers book, check, or reschedule appointments.
244
-
245
- Rules:
246
- - Keep responses to 1-2 sentences max
247
- - Always confirm dates and times back to the caller
248
- - Be warm but efficient`,
249
- begin_message: 'Hello! Thanks for calling Qualia Solutions. How can I help you today?',
250
- general_tools: [
251
- {
252
- type: 'end_call',
253
- name: 'end_call',
254
- description: 'End the call when the conversation is complete',
255
- },
256
- {
257
- type: 'custom',
258
- name: 'book_appointment',
259
- description: 'Book a new appointment for the caller',
260
- parameters: {
261
- type: 'object',
262
- properties: {
263
- date: { type: 'string', description: 'Appointment date in YYYY-MM-DD format' },
264
- time: { type: 'string', description: 'Appointment time in HH:MM format (24h)' },
265
- service: { type: 'string', description: 'Type of service requested' },
266
- caller_name: { type: 'string', description: 'Full name of the caller' },
267
- },
268
- required: ['date', 'time', 'service', 'caller_name'],
269
- },
270
- url: 'https://YOUR_PROJECT.supabase.co/functions/v1/retell-tool-handler',
271
- speak_during_execution: true,
272
- speak_after_execution: true,
273
- execution_message_description: 'Let the caller know you are checking availability and booking their appointment.',
274
- },
275
- {
276
- type: 'custom',
277
- name: 'check_availability',
278
- description: 'Check available appointment slots for a given date',
279
- parameters: {
280
- type: 'object',
281
- properties: {
282
- date: { type: 'string', description: 'Date to check in YYYY-MM-DD format' },
283
- },
284
- required: ['date'],
285
- },
286
- url: 'https://YOUR_PROJECT.supabase.co/functions/v1/retell-tool-handler',
287
- speak_during_execution: true,
288
- speak_after_execution: true,
289
- execution_message_description: 'Let the caller know you are checking available times.',
290
- },
291
- ],
292
- states: [
293
- {
294
- name: 'greeting',
295
- state_prompt: 'Greet the caller warmly and ask how you can help.',
296
- transitions: [
297
- {
298
- name: 'booking_intent',
299
- description: 'Caller wants to book an appointment',
300
- destination: 'collect_info',
301
- },
302
- {
303
- name: 'check_intent',
304
- description: 'Caller wants to check an existing appointment',
305
- destination: 'status_check',
306
- },
307
- {
308
- name: 'other_intent',
309
- description: 'Caller has a general question',
310
- destination: 'general_help',
311
- },
312
- ],
313
- },
314
- {
315
- name: 'collect_info',
316
- state_prompt: 'Collect the date, time, service type, and caller name. Use check_availability before booking. Then use book_appointment to confirm.',
317
- transitions: [
318
- {
319
- name: 'booking_complete',
320
- description: 'Appointment has been booked successfully',
321
- destination: 'farewell',
322
- },
323
- ],
324
- },
325
- {
326
- name: 'status_check',
327
- state_prompt: 'Ask for the caller phone number or name to look up their appointment.',
328
- transitions: [
329
- {
330
- name: 'status_done',
331
- description: 'Status has been communicated to caller',
332
- destination: 'farewell',
333
- },
334
- ],
335
- },
336
- {
337
- name: 'general_help',
338
- state_prompt: 'Answer the caller question. If you cannot help, offer to transfer to a human.',
339
- transitions: [
340
- {
341
- name: 'help_done',
342
- description: 'Question answered',
343
- destination: 'farewell',
344
- },
345
- ],
346
- },
347
- {
348
- name: 'farewell',
349
- state_prompt: 'Thank the caller and end the call politely. Use end_call tool.',
350
- transitions: [],
351
- },
352
- ],
353
- starting_state: 'greeting',
354
- }),
355
- });
356
-
357
- const llmData = await llmResponse.json();
358
- const llmId = llmData.llm_id;
359
-
360
- // Now create the agent (connects voice + LLM)
361
- const agentResponse = await fetch('https://api.retellai.com/v2/create-agent', {
362
- method: 'POST',
363
- headers: {
364
- 'Authorization': `Bearer ${RETELL_API_KEY}`,
365
- 'Content-Type': 'application/json',
366
- },
367
- body: JSON.stringify({
368
- agent_name: 'Qualia Appointment Scheduler',
369
- voice_id: '21m00Tcm4TlvDq8ikWAM', // ElevenLabs Rachel
370
- response_engine: {
371
- type: 'retell-llm',
372
- llm_id: llmId,
373
- },
374
- language: 'en-US',
375
- voice_temperature: 1,
376
- voice_speed: 1,
377
- responsiveness: 1,
378
- interruption_sensitivity: 1,
379
- enable_backchannel: true,
380
- backchannel_frequency: 0.8,
381
- reminder_trigger_ms: 10000,
382
- reminder_max_count: 2,
383
- ambient_sound: null,
384
- webhook_url: 'https://YOUR_PROJECT.supabase.co/functions/v1/retell-webhook',
385
- post_call_analysis_data: [
386
- {
387
- type: 'enum',
388
- name: 'call_outcome',
389
- description: 'The outcome of the call',
390
- options: ['appointment_booked', 'appointment_checked', 'general_inquiry', 'transferred', 'abandoned'],
391
- },
392
- {
393
- type: 'string',
394
- name: 'call_summary',
395
- description: 'A brief 1-2 sentence summary of what happened on the call',
396
- },
397
- {
398
- type: 'boolean',
399
- name: 'caller_satisfied',
400
- description: 'Whether the caller seemed satisfied with the interaction',
401
- },
402
- ],
403
- }),
404
- });
405
-
406
- const agentData = await agentResponse.json();
407
- console.log('Agent created:', agentData.agent_id);
408
- ```
409
-
410
- ### Assign Phone Number
411
-
412
- ```typescript
413
- // Purchase and assign a phone number
414
- const phoneResponse = await fetch('https://api.retellai.com/v2/create-phone-number', {
415
- method: 'POST',
416
- headers: {
417
- 'Authorization': `Bearer ${RETELL_API_KEY}`,
418
- 'Content-Type': 'application/json',
419
- },
420
- body: JSON.stringify({
421
- agent_id: agentData.agent_id,
422
- area_code: 357, // Cyprus area code (adjust per project)
423
- }),
424
- });
425
-
426
- const phoneData = await phoneResponse.json();
427
- console.log('Phone number assigned:', phoneData.phone_number);
428
- ```
429
-
430
- ### Import Existing Number (Telnyx/Twilio)
431
-
432
- ```typescript
433
- // If bringing your own number via Telnyx
434
- const importResponse = await fetch('https://api.retellai.com/v2/import-phone-number', {
435
- method: 'POST',
436
- headers: {
437
- 'Authorization': `Bearer ${RETELL_API_KEY}`,
438
- 'Content-Type': 'application/json',
439
- },
440
- body: JSON.stringify({
441
- agent_id: agentData.agent_id,
442
- phone_number: '+35712345678',
443
- telephony_provider: 'telnyx', // 'telnyx' | 'twilio' | 'vonage'
444
- telephony_credentials: {
445
- telnyx_api_key: Deno.env.get('TELNYX_API_KEY'),
446
- telnyx_app_connection_id: Deno.env.get('TELNYX_CONNECTION_ID'),
447
- },
448
- }),
449
- });
450
- ```
451
-
452
- ### Update Agent (PATCH)
453
-
454
- ```typescript
455
- // Update an existing agent
456
- await fetch(`https://api.retellai.com/v2/update-agent/${agentId}`, {
457
- method: 'PATCH',
458
- headers: {
459
- 'Authorization': `Bearer ${RETELL_API_KEY}`,
460
- 'Content-Type': 'application/json',
461
- },
462
- body: JSON.stringify({
463
- voice_id: 'NEW_VOICE_ID',
464
- webhook_url: 'https://YOUR_PROJECT.supabase.co/functions/v1/retell-webhook',
465
- }),
466
- });
467
- ```
468
-
469
- ---
470
-
471
- ## Stage 3: Setup Supabase Backend
472
-
473
- ### Database Schema
474
-
475
- ```sql
476
- -- Migration: supabase/migrations/YYYYMMDD_voice_agent_tables.sql
477
-
478
- -- Call logs — every call tracked
479
- CREATE TABLE call_logs (
480
- id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
481
- call_id TEXT UNIQUE NOT NULL, -- Retell call ID
482
- agent_id TEXT NOT NULL, -- Retell agent ID
483
- from_number TEXT, -- Caller phone
484
- to_number TEXT, -- Agent phone
485
- direction TEXT NOT NULL DEFAULT 'inbound'
486
- CHECK (direction IN ('inbound', 'outbound', 'web')),
487
- call_status TEXT NOT NULL DEFAULT 'started'
488
- CHECK (call_status IN ('started', 'ended', 'error')),
489
- disconnection_reason TEXT, -- 'user_hangup', 'agent_hangup', 'call_transfer', 'error', etc.
490
- duration_ms INTEGER,
491
- transcript TEXT,
492
- transcript_object JSONB, -- Full structured transcript
493
- call_analysis JSONB, -- Post-call analysis results
494
- recording_url TEXT,
495
- metadata JSONB DEFAULT '{}',
496
- started_at TIMESTAMPTZ DEFAULT NOW(),
497
- ended_at TIMESTAMPTZ,
498
- created_at TIMESTAMPTZ DEFAULT NOW()
499
- );
500
-
501
- -- Agent state — persistent context between calls per phone number
502
- CREATE TABLE agent_state (
503
- id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
504
- phone_number TEXT UNIQUE NOT NULL,
505
- agent_id TEXT NOT NULL,
506
- last_call_id TEXT REFERENCES call_logs(call_id),
507
- context JSONB DEFAULT '{}', -- Arbitrary state (name, preferences, history)
508
- call_count INTEGER DEFAULT 0,
509
- first_call_at TIMESTAMPTZ DEFAULT NOW(),
510
- last_call_at TIMESTAMPTZ DEFAULT NOW(),
511
- updated_at TIMESTAMPTZ DEFAULT NOW()
512
- );
513
-
514
- -- Appointments — example domain table
515
- CREATE TABLE appointments (
516
- id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
517
- call_id TEXT REFERENCES call_logs(call_id),
518
- customer_phone TEXT NOT NULL,
519
- customer_name TEXT,
520
- service_type TEXT NOT NULL,
521
- appointment_date DATE NOT NULL,
522
- appointment_time TIME NOT NULL,
523
- status TEXT DEFAULT 'scheduled'
524
- CHECK (status IN ('scheduled', 'confirmed', 'cancelled', 'completed', 'no_show')),
525
- notes TEXT,
526
- reminder_sent BOOLEAN DEFAULT FALSE,
527
- created_at TIMESTAMPTZ DEFAULT NOW(),
528
- updated_at TIMESTAMPTZ DEFAULT NOW()
529
- );
530
-
531
- -- Indexes
532
- CREATE INDEX idx_call_logs_call_id ON call_logs(call_id);
533
- CREATE INDEX idx_call_logs_from_number ON call_logs(from_number);
534
- CREATE INDEX idx_call_logs_started_at ON call_logs(started_at DESC);
535
- CREATE INDEX idx_agent_state_phone ON agent_state(phone_number);
536
- CREATE INDEX idx_appointments_date ON appointments(appointment_date);
537
- CREATE INDEX idx_appointments_phone ON appointments(customer_phone);
538
-
539
- -- Enable RLS on all tables
540
- ALTER TABLE call_logs ENABLE ROW LEVEL SECURITY;
541
- ALTER TABLE agent_state ENABLE ROW LEVEL SECURITY;
542
- ALTER TABLE appointments ENABLE ROW LEVEL SECURITY;
543
-
544
- -- RLS Policies: service_role only (Edge Functions use service_role key)
545
- -- These tables are written to by webhooks, not by end users directly.
546
- CREATE POLICY "Service role full access" ON call_logs
547
- FOR ALL USING (auth.role() = 'service_role');
548
-
549
- CREATE POLICY "Service role full access" ON agent_state
550
- FOR ALL USING (auth.role() = 'service_role');
551
-
552
- CREATE POLICY "Service role full access" ON appointments
553
- FOR ALL USING (auth.role() = 'service_role');
554
-
555
- -- If you also need an admin dashboard with authenticated users:
556
- -- CREATE POLICY "Admins can read call logs" ON call_logs
557
- -- FOR SELECT USING (
558
- -- EXISTS (
559
- -- SELECT 1 FROM profiles
560
- -- WHERE profiles.id = auth.uid()
561
- -- AND profiles.role = 'admin'
562
- -- )
563
- -- );
564
-
565
- -- Updated_at trigger
566
- CREATE OR REPLACE FUNCTION update_updated_at()
567
- RETURNS TRIGGER AS $$
568
- BEGIN
569
- NEW.updated_at = NOW();
570
- RETURN NEW;
571
- END;
572
- $$ LANGUAGE plpgsql;
573
-
574
- CREATE TRIGGER tr_appointments_updated
575
- BEFORE UPDATE ON appointments
576
- FOR EACH ROW EXECUTE FUNCTION update_updated_at();
577
-
578
- CREATE TRIGGER tr_agent_state_updated
579
- BEFORE UPDATE ON agent_state
580
- FOR EACH ROW EXECUTE FUNCTION update_updated_at();
581
- ```
582
-
583
- ---
584
-
585
- ## Stage 4: Configure Webhooks
586
-
587
- ### Retell Webhook Events
588
-
589
- Retell sends POST requests to your `webhook_url` with these event types:
590
-
591
- | Event | When | Key Data |
592
- |-------|------|----------|
593
- | `call_started` | Call begins | call_id, from_number, to_number, agent_id |
594
- | `call_ended` | Call finishes | call_id, duration_ms, transcript, recording_url, disconnection_reason |
595
- | `call_analyzed` | Post-call analysis complete | call_id, call_analysis (custom fields you defined) |
596
- | `tool_call_invoked` | Agent calls a custom tool | tool_call_id, name, arguments |
597
- | `tool_call_result` | Tool execution completed | tool_call_id, result |
598
-
599
- ### Webhook Signature Verification
600
-
601
- Retell signs webhooks using `x-retell-signature` header with HMAC-SHA256.
602
-
603
- ```typescript
604
- import { createHmac } from "node:crypto";
605
-
606
- function verifyRetellSignature(
607
- body: string,
608
- signature: string | null,
609
- apiKey: string
610
- ): boolean {
611
- if (!signature) return false;
612
-
613
- const hmac = createHmac('sha256', apiKey);
614
- hmac.update(body);
615
- const expectedSignature = hmac.digest('base64');
616
-
617
- return signature === expectedSignature;
618
- }
619
- ```
620
-
621
- > **Important:** Retell uses the API key itself as the HMAC secret for webhook verification. Not a separate webhook secret.
622
-
623
- ### Webhook Handler (Supabase Edge Function)
624
-
625
- ```typescript
626
- // supabase/functions/retell-webhook/index.ts
627
- import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
628
- import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
629
- import { createHmac } from "node:crypto";
630
- import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
631
-
632
- // --- Signature Verification ---
633
- function verifyRetellSignature(body: string, signature: string | null): boolean {
634
- if (!signature) return false;
635
- const apiKey = Deno.env.get('RETELL_API_KEY')!;
636
- const hmac = createHmac('sha256', apiKey);
637
- hmac.update(body);
638
- const expected = hmac.digest('base64');
639
- return signature === expected;
640
- }
641
-
642
- // --- Zod Schemas ---
643
- const CallStartedSchema = z.object({
644
- event: z.literal('call_started'),
645
- call: z.object({
646
- call_id: z.string(),
647
- agent_id: z.string(),
648
- from_number: z.string().optional(),
649
- to_number: z.string().optional(),
650
- direction: z.enum(['inbound', 'outbound', 'web']),
651
- metadata: z.record(z.unknown()).optional(),
652
- }),
653
- });
654
-
655
- const CallEndedSchema = z.object({
656
- event: z.literal('call_ended'),
657
- call: z.object({
658
- call_id: z.string(),
659
- agent_id: z.string(),
660
- from_number: z.string().optional(),
661
- to_number: z.string().optional(),
662
- direction: z.enum(['inbound', 'outbound', 'web']),
663
- duration_ms: z.number().optional(),
664
- transcript: z.string().optional(),
665
- transcript_object: z.array(z.unknown()).optional(),
666
- recording_url: z.string().optional(),
667
- disconnection_reason: z.string().optional(),
668
- metadata: z.record(z.unknown()).optional(),
669
- }),
670
- });
671
-
672
- const CallAnalyzedSchema = z.object({
673
- event: z.literal('call_analyzed'),
674
- call: z.object({
675
- call_id: z.string(),
676
- call_analysis: z.record(z.unknown()).optional(),
677
- }),
678
- });
679
-
680
- // --- Main Handler ---
681
- serve(async (req: Request) => {
682
- // Only accept POST
683
- if (req.method !== 'POST') {
684
- return new Response('Method not allowed', { status: 405 });
685
- }
686
-
687
- const body = await req.text();
688
-
689
- // 1. Verify webhook signature
690
- const signature = req.headers.get('x-retell-signature');
691
- if (!verifyRetellSignature(body, signature)) {
692
- console.error('Invalid webhook signature');
693
- return new Response('Unauthorized', { status: 401 });
694
- }
695
-
696
- // 2. Parse body
697
- let payload: unknown;
698
- try {
699
- payload = JSON.parse(body);
700
- } catch {
701
- return new Response('Invalid JSON', { status: 400 });
702
- }
703
-
704
- // 3. Initialize Supabase client (service role for DB writes)
705
- const supabase = createClient(
706
- Deno.env.get('SUPABASE_URL')!,
707
- Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
708
- );
709
-
710
- // 4. Route by event type
711
- const event = (payload as { event?: string }).event;
712
-
713
- try {
714
- switch (event) {
715
- case 'call_started': {
716
- const parsed = CallStartedSchema.parse(payload);
717
- const { call } = parsed;
718
-
719
- // Insert initial call log
720
- await supabase.from('call_logs').insert({
721
- call_id: call.call_id,
722
- agent_id: call.agent_id,
723
- from_number: call.from_number ?? null,
724
- to_number: call.to_number ?? null,
725
- direction: call.direction,
726
- call_status: 'started',
727
- metadata: call.metadata ?? {},
728
- });
729
-
730
- // Update agent state (track returning callers)
731
- if (call.from_number) {
732
- await supabase.from('agent_state').upsert(
733
- {
734
- phone_number: call.from_number,
735
- agent_id: call.agent_id,
736
- last_call_id: call.call_id,
737
- last_call_at: new Date().toISOString(),
738
- call_count: 1, // Will be incremented via raw SQL below
739
- },
740
- { onConflict: 'phone_number' }
741
- );
742
-
743
- // Increment call count for returning callers
744
- await supabase.rpc('increment_call_count', {
745
- p_phone: call.from_number,
746
- });
747
- }
748
-
749
- console.log(`Call started: ${call.call_id}`);
750
- break;
751
- }
752
-
753
- case 'call_ended': {
754
- const parsed = CallEndedSchema.parse(payload);
755
- const { call } = parsed;
756
-
757
- // Update call log with final data
758
- await supabase
759
- .from('call_logs')
760
- .update({
761
- call_status: 'ended',
762
- duration_ms: call.duration_ms ?? null,
763
- transcript: call.transcript ?? null,
764
- transcript_object: call.transcript_object ?? null,
765
- recording_url: call.recording_url ?? null,
766
- disconnection_reason: call.disconnection_reason ?? null,
767
- ended_at: new Date().toISOString(),
768
- })
769
- .eq('call_id', call.call_id);
770
-
771
- console.log(`Call ended: ${call.call_id} (${call.duration_ms}ms)`);
772
- break;
773
- }
774
-
775
- case 'call_analyzed': {
776
- const parsed = CallAnalyzedSchema.parse(payload);
777
- const { call } = parsed;
778
-
779
- // Store post-call analysis
780
- await supabase
781
- .from('call_logs')
782
- .update({
783
- call_analysis: call.call_analysis ?? null,
784
- })
785
- .eq('call_id', call.call_id);
786
-
787
- console.log(`Call analyzed: ${call.call_id}`);
788
- break;
789
- }
790
-
791
- default:
792
- console.log(`Unhandled event type: ${event}`);
793
- }
794
- } catch (error) {
795
- console.error(`Error processing ${event}:`, error);
796
- // Return 200 anyway to prevent Retell from retrying on validation errors.
797
- // Log the error for debugging. Only return 5xx for transient failures.
798
- }
799
-
800
- return new Response(JSON.stringify({ received: true }), {
801
- status: 200,
802
- headers: { 'Content-Type': 'application/json' },
803
- });
804
- });
805
- ```
806
-
807
- ### Helper RPC for Call Count Increment
808
-
809
- ```sql
810
- -- Migration: supabase/migrations/YYYYMMDD_increment_call_count.sql
811
- CREATE OR REPLACE FUNCTION increment_call_count(p_phone TEXT)
812
- RETURNS VOID AS $$
813
- BEGIN
814
- UPDATE agent_state
815
- SET call_count = call_count + 1
816
- WHERE phone_number = p_phone;
817
- END;
818
- $$ LANGUAGE plpgsql SECURITY DEFINER;
819
- ```
820
-
821
- ### Tool Handler (Supabase Edge Function)
822
-
823
- Retell calls your custom tool URLs directly. Each tool gets its own handler or a unified one:
824
-
825
- ```typescript
826
- // supabase/functions/retell-tool-handler/index.ts
827
- import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
828
- import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
829
- import { z } from "https://deno.land/x/zod@v3.22.4/mod.ts";
830
-
831
- // --- Zod Schemas for Tool Arguments ---
832
- const BookAppointmentArgs = z.object({
833
- date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'Date must be YYYY-MM-DD'),
834
- time: z.string().regex(/^\d{2}:\d{2}$/, 'Time must be HH:MM'),
835
- service: z.string().min(1),
836
- caller_name: z.string().min(1),
837
- });
838
-
839
- const CheckAvailabilityArgs = z.object({
840
- date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/),
841
- });
842
-
843
- // --- Main Handler ---
844
- serve(async (req: Request) => {
845
- if (req.method !== 'POST') {
846
- return new Response('Method not allowed', { status: 405 });
847
- }
848
-
849
- const body = await req.json();
850
-
851
- // Retell sends: { tool_call_id, name, arguments, call }
852
- const { name, arguments: args, call } = body;
853
-
854
- const supabase = createClient(
855
- Deno.env.get('SUPABASE_URL')!,
856
- Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!
857
- );
858
-
859
- try {
860
- switch (name) {
861
- case 'book_appointment': {
862
- const parsed = BookAppointmentArgs.parse(args);
863
-
864
- // Check if slot is taken
865
- const { data: existing } = await supabase
866
- .from('appointments')
867
- .select('id')
868
- .eq('appointment_date', parsed.date)
869
- .eq('appointment_time', parsed.time)
870
- .neq('status', 'cancelled')
871
- .maybeSingle();
872
-
873
- if (existing) {
874
- return Response.json({
875
- result: `Sorry, that time slot on ${parsed.date} at ${parsed.time} is already booked. Please choose another time.`,
876
- });
877
- }
878
-
879
- // Book the appointment
880
- const { data: appointment, error } = await supabase
881
- .from('appointments')
882
- .insert({
883
- call_id: call?.call_id ?? null,
884
- customer_phone: call?.from_number ?? 'unknown',
885
- customer_name: parsed.caller_name,
886
- service_type: parsed.service,
887
- appointment_date: parsed.date,
888
- appointment_time: parsed.time,
889
- })
890
- .select()
891
- .single();
892
-
893
- if (error) throw error;
894
-
895
- return Response.json({
896
- result: `Appointment booked successfully for ${parsed.caller_name} on ${parsed.date} at ${parsed.time} for ${parsed.service}. Appointment ID: ${appointment.id}.`,
897
- });
898
- }
899
-
900
- case 'check_availability': {
901
- const parsed = CheckAvailabilityArgs.parse(args);
902
-
903
- // Get booked slots for that date
904
- const { data: booked } = await supabase
905
- .from('appointments')
906
- .select('appointment_time')
907
- .eq('appointment_date', parsed.date)
908
- .neq('status', 'cancelled');
909
-
910
- const bookedTimes = new Set((booked ?? []).map(b => b.appointment_time));
911
-
912
- // Generate available slots (9 AM to 5 PM, 30-min intervals)
913
- const allSlots = [];
914
- for (let h = 9; h < 17; h++) {
915
- for (const m of ['00', '30']) {
916
- const slot = `${h.toString().padStart(2, '0')}:${m}`;
917
- if (!bookedTimes.has(slot)) {
918
- allSlots.push(slot);
919
- }
920
- }
921
- }
922
-
923
- if (allSlots.length === 0) {
924
- return Response.json({
925
- result: `No available slots on ${parsed.date}. Please try another date.`,
926
- });
927
- }
928
-
929
- return Response.json({
930
- result: `Available times on ${parsed.date}: ${allSlots.join(', ')}`,
931
- });
932
- }
933
-
934
- default:
935
- return Response.json({
936
- result: `Unknown tool: ${name}`,
937
- });
938
- }
939
- } catch (error) {
940
- console.error(`Tool ${name} error:`, error);
941
-
942
- if (error instanceof z.ZodError) {
943
- return Response.json({
944
- result: 'I need a bit more information. Could you provide the date and time again?',
945
- });
946
- }
947
-
948
- return Response.json({
949
- result: 'Sorry, I encountered an error processing your request. Let me try again.',
950
- });
951
- }
952
- });
953
- ```
954
-
955
- > **Key pattern:** Tool handlers return `{ result: "..." }`. The `result` string is what the agent speaks back to the caller. Write it as natural speech, not JSON data.
956
-
957
- ---
958
-
959
- ## Stage 5: Deploy & Test
960
-
961
- ### Deploy Steps
962
-
963
- ```bash
964
- # 1. Apply database migration
965
- supabase db push
966
-
967
- # 2. Set secrets for Edge Functions
968
- supabase secrets set RETELL_API_KEY=key_...
969
- supabase secrets set ELEVENLABS_API_KEY=xi_...
970
-
971
- # 3. Deploy Edge Functions
972
- supabase functions deploy retell-webhook --no-verify-jwt
973
- supabase functions deploy retell-tool-handler --no-verify-jwt
974
-
975
- # 4. Get the function URLs
976
- # https://YOUR_PROJECT.supabase.co/functions/v1/retell-webhook
977
- # https://YOUR_PROJECT.supabase.co/functions/v1/retell-tool-handler
978
-
979
- # 5. Update Retell agent with webhook URL (via API or dashboard)
980
- curl -X PATCH "https://api.retellai.com/v2/update-agent/YOUR_AGENT_ID" \
981
- -H "Authorization: Bearer $RETELL_API_KEY" \
982
- -H "Content-Type: application/json" \
983
- -d '{"webhook_url": "https://YOUR_PROJECT.supabase.co/functions/v1/retell-webhook"}'
984
-
985
- # 6. Test with a web call (no phone needed)
986
- curl -X POST "https://api.retellai.com/v2/create-web-call" \
987
- -H "Authorization: Bearer $RETELL_API_KEY" \
988
- -H "Content-Type: application/json" \
989
- -d '{"agent_id": "YOUR_AGENT_ID"}'
990
- # Returns: { "call_id": "...", "access_token": "..." }
991
- # Use access_token with Retell Web SDK to test in browser
992
-
993
- # 7. Test with a phone call
994
- curl -X POST "https://api.retellai.com/v2/create-phone-call" \
995
- -H "Authorization: Bearer $RETELL_API_KEY" \
996
- -H "Content-Type: application/json" \
997
- -d '{
998
- "from_number": "+1234567890",
999
- "to_number": "+0987654321",
1000
- "agent_id": "YOUR_AGENT_ID"
1001
- }'
1002
- ```
1003
-
1004
- > **Important:** Use `--no-verify-jwt` for webhook and tool handler functions. Retell authenticates via `x-retell-signature`, not JWT. The functions verify signatures themselves.
1005
-
1006
- ### Test Scenarios
1007
-
1008
- | # | Scenario | Expected Outcome |
1009
- |---|----------|------------------|
1010
- | 1 | Happy path: book appointment | Agent collects info, calls book_appointment tool, confirms booking |
1011
- | 2 | Slot conflict | Agent reports slot unavailable, suggests alternatives |
1012
- | 3 | Invalid date from caller | Agent asks caller to repeat/clarify the date |
1013
- | 4 | Caller interrupts mid-sentence | Agent stops speaking and listens |
1014
- | 5 | Caller asks for human | Agent transfers (or apologizes if no transfer configured) |
1015
- | 6 | Returning caller | agent_state context loaded, call_count incremented |
1016
- | 7 | Webhook failure | Returns 200 anyway, error logged for debugging |
1017
- | 8 | Silence timeout | Agent nudges after reminder_trigger_ms, ends after max nudges |
1018
-
1019
- ### Verify Webhook Delivery
1020
-
1021
- ```bash
1022
- # Check Retell webhook logs via API
1023
- curl "https://api.retellai.com/v2/list-calls?agent_id=YOUR_AGENT_ID&limit=5" \
1024
- -H "Authorization: Bearer $RETELL_API_KEY" | jq '.[]|{call_id,call_status,disconnection_reason,duration_ms}'
1025
-
1026
- # Check Supabase Edge Function logs
1027
- supabase functions logs retell-webhook --limit 20
1028
-
1029
- # Verify data landed in DB
1030
- supabase db execute "SELECT call_id, call_status, duration_ms, ended_at FROM call_logs ORDER BY created_at DESC LIMIT 5"
1031
- ```
1032
-
1033
- ---
1034
-
1035
- ## Voice Selection (ElevenLabs)
1036
-
1037
- ### Pre-built Voices
1038
-
1039
- | Use Case | Voice Name | Voice ID | Style | Best For |
1040
- |----------|-----------|----------|-------|----------|
1041
- | Professional Female | Rachel | `21m00Tcm4TlvDq8ikWAM` | Calm, clear, authoritative | Medical, legal, enterprise |
1042
- | Friendly Female | Bella | `EXAVITQu4vr4xnSDxMaL` | Warm, approachable, casual | Retail, hospitality, support |
1043
- | Energetic Female | Elli | `MF3mGyEYCl7XYWbV9V6O` | Upbeat, enthusiastic | Sales, marketing, events |
1044
- | Professional Male | Adam | `pNInz6obpgDQGcFmaJgB` | Deep, confident, steady | Finance, corporate, B2B |
1045
- | Conversational Male | Antoni | `ErXwobaYiN019PkySvjV` | Natural, relaxed | General-purpose, support |
1046
- | Warm Male | Josh | `TxGEqnHWrfWFTfGW9XjX` | Friendly, trustworthy | Healthcare, insurance |
1047
- | Authoritative Male | Arnold | `VR6AewLTigWG4xSOukaG` | Strong, commanding | Emergency, security |
1048
- | Young Female | Dorothy | `ThT5KcBeYPX3keUQqHPh` | Bright, clear | Startups, tech support |
1049
-
1050
- ### Multilingual Voices
1051
-
1052
- | Language | Voice Name | Voice ID | Notes |
1053
- |----------|-----------|----------|-------|
1054
- | Arabic | Custom clone | (clone your own) | Best results with voice cloning |
1055
- | Greek | Custom clone | (clone your own) | Limited pre-built options |
1056
- | Multilingual | Eleven Multilingual v2 | Any v2 voice | All v2 voices support 29 languages |
1057
-
1058
- > **Tip:** All ElevenLabs voices with "v2" or "Multilingual v2" model support automatic language detection across 29 languages. Use these for multilingual agents.
1059
-
1060
- ### Voice Cloning (Custom Brand Voice)
1061
-
1062
- ```typescript
1063
- // Clone a voice from audio samples
1064
- const formData = new FormData();
1065
- formData.append('name', 'Brand Voice');
1066
- formData.append('description', 'Company brand voice for phone agents');
1067
- formData.append('files', audioFile1); // Min 1 sample, recommend 3+ minutes total
1068
- formData.append('files', audioFile2);
1069
- formData.append('labels', JSON.stringify({ accent: 'neutral', gender: 'female' }));
1070
-
1071
- const response = await fetch('https://api.elevenlabs.io/v1/voices/add', {
1072
- method: 'POST',
1073
- headers: {
1074
- 'xi-api-key': ELEVENLABS_API_KEY,
1075
- },
1076
- body: formData,
1077
- });
1078
-
1079
- const voice = await response.json();
1080
- console.log('Cloned voice ID:', voice.voice_id);
1081
- // Use this voice_id when creating the Retell agent
1082
- ```
1083
-
1084
- ### List Available Voices
1085
-
1086
- ```typescript
1087
- // Fetch all available voices from ElevenLabs
1088
- const response = await fetch('https://api.elevenlabs.io/v1/voices', {
1089
- headers: { 'xi-api-key': ELEVENLABS_API_KEY },
1090
- });
1091
- const { voices } = await response.json();
1092
-
1093
- voices.forEach((v: { name: string; voice_id: string; labels: Record<string, string> }) => {
1094
- console.log(`${v.name} (${v.voice_id}) — ${v.labels?.accent ?? 'unknown accent'}, ${v.labels?.gender ?? ''}`);
1095
- });
1096
- ```
1097
-
1098
- ---
1099
-
1100
- ## Advanced Patterns
1101
-
1102
- ### Outbound Calling (Proactive Calls)
1103
-
1104
- ```typescript
1105
- // Trigger an outbound call (e.g., appointment reminders)
1106
- async function makeOutboundCall(
1107
- agentId: string,
1108
- fromNumber: string,
1109
- toNumber: string,
1110
- metadata?: Record<string, string>
1111
- ) {
1112
- const response = await fetch('https://api.retellai.com/v2/create-phone-call', {
1113
- method: 'POST',
1114
- headers: {
1115
- 'Authorization': `Bearer ${RETELL_API_KEY}`,
1116
- 'Content-Type': 'application/json',
1117
- },
1118
- body: JSON.stringify({
1119
- agent_id: agentId,
1120
- from_number: fromNumber,
1121
- to_number: toNumber,
1122
- metadata: metadata ?? {},
1123
- retell_llm_dynamic_variables: {
1124
- // Inject variables into the LLM prompt at call time
1125
- customer_name: 'John',
1126
- appointment_date: '2026-04-05',
1127
- appointment_time: '14:00',
1128
- },
1129
- }),
1130
- });
1131
-
1132
- return response.json();
1133
- }
1134
-
1135
- // Example: reminder cron job (Supabase Edge Function + pg_cron)
1136
- // Run daily at 8 AM, call patients with appointments tomorrow
1137
- ```
1138
-
1139
- ### Dynamic Variables (Personalization)
1140
-
1141
- Retell supports `retell_llm_dynamic_variables` to inject context into the LLM prompt at call time:
1142
-
1143
- ```typescript
1144
- // In your LLM prompt, use {{variable_name}} syntax:
1145
- // "Hello {{customer_name}}, I'm calling about your appointment on {{appointment_date}}."
1146
-
1147
- // When creating the call, pass the variables:
1148
- const call = await fetch('https://api.retellai.com/v2/create-phone-call', {
1149
- method: 'POST',
1150
- headers: {
1151
- 'Authorization': `Bearer ${RETELL_API_KEY}`,
1152
- 'Content-Type': 'application/json',
1153
- },
1154
- body: JSON.stringify({
1155
- agent_id: agentId,
1156
- from_number: fromNumber,
1157
- to_number: toNumber,
1158
- retell_llm_dynamic_variables: {
1159
- customer_name: 'Sarah',
1160
- appointment_date: 'April 5th',
1161
- company_name: 'Qualia Solutions',
1162
- },
1163
- }),
1164
- });
1165
- ```
1166
-
1167
- ### Call Transfer
1168
-
1169
- ```typescript
1170
- // In your Retell LLM config, add a transfer tool:
1171
- {
1172
- type: 'transfer_call',
1173
- name: 'transfer_to_human',
1174
- description: 'Transfer the caller to a human agent when they request one or when you cannot help',
1175
- number: '+35799999999', // Human agent / call center number
1176
- }
1177
- ```
1178
-
1179
- ### Web Call (Browser-Based)
1180
-
1181
- ```typescript
1182
- // Create a web call for browser-based voice agents
1183
- const webCall = await fetch('https://api.retellai.com/v2/create-web-call', {
1184
- method: 'POST',
1185
- headers: {
1186
- 'Authorization': `Bearer ${RETELL_API_KEY}`,
1187
- 'Content-Type': 'application/json',
1188
- },
1189
- body: JSON.stringify({
1190
- agent_id: agentId,
1191
- metadata: { source: 'website', page: '/contact' },
1192
- retell_llm_dynamic_variables: {
1193
- customer_name: 'Website Visitor',
1194
- },
1195
- }),
1196
- });
1197
-
1198
- const { access_token } = await webCall.json();
1199
- // Pass access_token to Retell Web SDK on the frontend
1200
- ```
1201
-
1202
- ### Frontend Web SDK Integration
1203
-
1204
- ```typescript
1205
- // npm install retell-client-js-sdk
1206
- import { RetellWebClient } from 'retell-client-js-sdk';
1207
-
1208
- const retellClient = new RetellWebClient();
1209
-
1210
- // Start call with access token from your backend
1211
- await retellClient.startCall({
1212
- accessToken: accessToken, // From create-web-call response
1213
- sampleRate: 24000,
1214
- emitRawAudioSamples: false,
1215
- });
1216
-
1217
- // Event handlers
1218
- retellClient.on('call_started', () => {
1219
- console.log('Call connected');
1220
- });
1221
-
1222
- retellClient.on('call_ended', () => {
1223
- console.log('Call ended');
1224
- });
1225
-
1226
- retellClient.on('agent_start_talking', () => {
1227
- // Update UI — agent is speaking
1228
- });
1229
-
1230
- retellClient.on('agent_stop_talking', () => {
1231
- // Update UI — agent stopped
1232
- });
1233
-
1234
- retellClient.on('error', (error) => {
1235
- console.error('Retell error:', error);
1236
- retellClient.stopCall();
1237
- });
1238
-
1239
- // End call
1240
- retellClient.stopCall();
1241
- ```
1242
-
1243
- ---
1244
-
1245
- ## Best Practices
1246
-
1247
- 1. **Keep responses short** — 1-2 sentences max. Long agent monologues make callers hang up.
1248
- 2. **Confirm critical info** — Always repeat dates, times, and names back before acting.
1249
- 3. **Handle interruptions** — Set `interruption_sensitivity` to 1 for natural conversation.
1250
- 4. **Use backchannels** — Enable `enable_backchannel` for natural "uh huh" filler while listening.
1251
- 5. **Tool results are speech** — Return natural language in `result`, not JSON. The agent speaks it.
1252
- 6. **Validate on the tool side** — Use Zod in tool handlers. Return friendly error messages, not stack traces.
1253
- 7. **Log everything** — Store full transcripts and call analysis for debugging and improvement.
1254
- 8. **Test with real calls** — Browser testing is fast, but always test real phone calls before going live.
1255
- 9. **Use post-call analysis** — Define `post_call_analysis_data` to auto-extract outcomes, sentiment, etc.
1256
- 10. **Signature verification is mandatory** — Never skip `x-retell-signature` verification on webhooks.
1257
- 11. **Ambient sound** — Use `coffee-shop` or `call-center` ambient sound for more natural feel.
1258
- 12. **Dynamic variables** — Use `retell_llm_dynamic_variables` for personalization on outbound calls.
1259
-
1260
- ## Retell API Quick Reference
1261
-
1262
- | Action | Method | Endpoint |
1263
- |--------|--------|----------|
1264
- | Create LLM | POST | `https://api.retellai.com/v2/create-retell-llm` |
1265
- | Create Agent | POST | `https://api.retellai.com/v2/create-agent` |
1266
- | Update Agent | PATCH | `https://api.retellai.com/v2/update-agent/{agent_id}` |
1267
- | Get Agent | GET | `https://api.retellai.com/v2/get-agent/{agent_id}` |
1268
- | List Agents | GET | `https://api.retellai.com/v2/list-agents` |
1269
- | Delete Agent | DELETE | `https://api.retellai.com/v2/delete-agent/{agent_id}` |
1270
- | Create Phone Number | POST | `https://api.retellai.com/v2/create-phone-number` |
1271
- | Import Phone Number | POST | `https://api.retellai.com/v2/import-phone-number` |
1272
- | Create Phone Call | POST | `https://api.retellai.com/v2/create-phone-call` |
1273
- | Create Web Call | POST | `https://api.retellai.com/v2/create-web-call` |
1274
- | List Calls | GET | `https://api.retellai.com/v2/list-calls` |
1275
- | Get Call | GET | `https://api.retellai.com/v2/get-call/{call_id}` |
1276
- | Update Retell LLM | PATCH | `https://api.retellai.com/v2/update-retell-llm/{llm_id}` |
1277
-
1278
- All endpoints require header: `Authorization: Bearer {RETELL_API_KEY}`
1279
-
1280
- ## Integration with Other Skills
1281
-
1282
- - **supabase** — Database operations, Edge Functions, migrations
1283
- - **docs-lookup** — Retell AI / ElevenLabs API documentation
1284
- - **deploy** — Production deployment pipeline
1285
- - **ship** — Full deploy with quality gates
1286
-
1287
- ## Key Decisions to Ask User
1288
-
1289
- - **Use case**: What should the agent do? (booking, support, sales, etc.)
1290
- - **Voice gender/style**: Professional, friendly, energetic? (see Voice Selection table)
1291
- - **Language**: English only or multilingual? (affects voice model selection)
1292
- - **Custom voice**: Need voice cloning for brand consistency?
1293
- - **Phone vs web**: Inbound phone calls, outbound calls, or browser widget?
1294
- - **LLM**: Retell built-in (Claude/GPT) or custom LLM via OpenRouter?
1295
- - **Transfer**: Should the agent be able to transfer to a human?
1296
- - **Telephony**: Buy new number from Retell, or import existing (Telnyx/Twilio)?
1297
-
1298
- ## Trigger Phrases
1299
-
1300
- - "build voice agent"
1301
- - "create voice assistant"
1302
- - "voice AI for"
1303
- - "phone bot for"
1304
- - "retell agent"
1305
- - "retell"
1306
- - "elevenlabs"
1307
- - "voice webhook"
1308
- - "call agent"
1309
- - "phone agent"
1310
- - "voice automation"
1311
- - "outbound calls"
1312
- - "web call widget"