One command starts everything: Postgres, n8n, the public tunnel, the workflow, Layla's tools and webhook, and the report's live connection.
One click puts Layla in out-of-hours mode (same agent, same number) and opens a new Demo shift, so calls during the meeting show up as tonight's shift on the Overnight page. The override lives in Postgres (app_settings), so tools, SLAs, the website greeting and the report all follow it. db/reset_demo.sh turns it off.
Reading the current mode…
VP_DEMO_PHONE. Only inside the UAE calling window (Mon–Fri 09:00–18:00) unless the override is on.
In VS Code, open the terminal (Ctrl + `). It opens in the project folder.
./start.sh
This takes about 30 seconds and opens the customer page and this report. Add --no-open to skip opening them, or --redeploy to push prompt or tool changes to Layla.
./stop.sh # stops the tunnel and Postgres (data is kept) ./stop.sh --all # also stops the n8n container
| Step | What happens | Where |
|---|---|---|
| 1 · Docker | Opens Docker Desktop if it isn't running | |
| 2 · Postgres | Starts voicepilot-postgres and applies db/schema.sql and db/seed.sql (safe to re-run) | localhost:5433 |
| 3 · n8n | Starts your local-n8n container and waits until it is healthy | localhost:5678 |
| 4 · Tunnel | Reuses the Cloudflare quick tunnel if it still works, otherwise starts a new one. ElevenLabs needs a public HTTPS URL. | *.trycloudflare.com |
| 5 · Workflow | Pushes n8n/voicepilot-construction.json with your key and the Postgres credential, then activates it | n8n |
| 6 · Layla | If the tunnel URL changed: updates the 10 tools, the post-call webhook, prompts, guardrails and voice | ElevenLabs |
| 7 · Report | Writes dashboard/config.js so the Report and Live data pages connect on their own | this page |
| 8 · Smoke test | Calls the KPI feed and a tool through the tunnel, the way ElevenLabs will |
cloudflared (brew install cloudflared).cp .env.example .env, then fill in ELEVENLABS_API_KEY, N8N_API_KEY, VOICEPILOT_KEY and your n8n container name.db/.env with POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB=voicepilot and VOICEPILOT_APP_PASSWORD../start.sh.Both .env files, .run/ and config.js are gitignored. The scripts never print keys.
Paste this into Claude Code in VS Code and it will bring the stack up and fix anything that fails.
Start the VoicePilot demo in this repo. Run ./start.sh --no-open and fix anything that fails (Docker Desktop not running, the n8n container stopped, Postgres not healthy, the tunnel not reachable). Then confirm, with evidence: 1. voicepilot-postgres is healthy on localhost:5433 and the voicepilot schema has customers and kb_articles. 2. The n8n workflow "VoicePilot AI | Construction customer care" is active and the tool, post-call, kpi and data webhooks answer locally. 3. The tunnel URL in .run/tunnel_url reaches n8n, and Layla's ElevenLabs tools and post-call webhook point at it. If they don't, run ./start.sh --redeploy. 4. A test call to the search_help_center tool through the tunnel creates a row in voicepilot.tool_events. Finally open web/index.html and dashboard/live.html and give me the URLs. Never print or commit anything from .env or db/.env.
Layla answers calls through a Twilio number (or a SIP trunk) connected in ElevenLabs. Caller ID lets her verify a customer without asking for their name.
Assign it to Layla. This removes it from whichever agent it is on now.
set -a; . ./.env; set +a python3 elevenlabs/assign_phone.py --list python3 elevenlabs/assign_phone.py +1XXXXXXXXXX
Buy a voice-capable number in Twilio. Then in ElevenLabs go to Phone numbers → Import → Twilio, give it the number, Account SID and Auth Token, and assign it to Layla. Or import it with assign_phone.py --import.
A UAE (+971) number for a real client needs a local SIP trunk and TDRA approval. A US or UK Twilio number is fine for testing.
Put your own mobile on a demo customer, then call the number:
docker exec voicepilot-postgres psql -U voicepilot_owner -d voicepilot -c "update voicepilot.customers set phone = '+9715XXXXXXXX' where customer_id = 'C-1002'"
Re-running db/setup.sh or ./start.sh resets the demo phone numbers.
api.us.elevenlabs.io/twilio/inbound_call). Importing it through ElevenLabs sets this.Only the Crestline website (site/) goes public. The report, live data and demo guide stay on your machine because they show customer data.
dashboard.ngrok.com. Copy Your Authtoken, then under Domains claim your free static domain (for example crestline-demo.ngrok-free.app).dash.cloudflare.com. Copy your Account ID (Workers & Pages overview, right side). Then go to My Profile → API Tokens → Create Token → Custom token, with permission Account → Cloudflare Pages → Edit..env:
NGROK_AUTHTOKEN=... NGROK_DOMAIN=your-name.ngrok-free.app CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... VP_PAGES_PROJECT=crestline-demo
./start.sh --redeploy once. Layla's tools, the post-call webhook and the website now use the permanent address, and the site is live at https://crestline-demo.pages.dev. Every later ./start.sh republishes it.The site and Layla only work while this Mac runs n8n and the tunnel. For always-on, move n8n and Postgres to a small cloud server.
Every booking, reschedule, cancellation, ticket and callback writes a WhatsApp message and an email, in the customer's language, to voicepilot.notifications (see Live data → Outbox). n8n sends them right after the tool call. Until a provider is connected they are marked simulated, so nothing reaches the fake demo numbers.
| Option | Good for | What you need | .env |
|---|---|---|---|
| Twilio WhatsApp sandbox | Fastest demo: working in about 10 minutes | Twilio account (free trial works). In WhatsApp, send the sandbox join code to +1 415 523 8886 from your phone. | VP_WHATSAPP_PROVIDER=twilio TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN |
| SMS from your Twilio number | Plain text messages, no WhatsApp setup | Your existing Twilio number. Trial accounts only text verified numbers. UAE networks often filter SMS from US numbers; WhatsApp is more reliable there. | VP_WHATSAPP_PROVIDER=sms TWILIO_SMS_FROM=+14244010688 TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN |
| Meta WhatsApp Cloud API | No Twilio; closest to production | Meta developer app with WhatsApp: a free test number, a token, and your phone added as a test recipient. Messages outside a 24-hour chat window need approved templates. | VP_WHATSAPP_PROVIDER=meta META_WA_TOKEN, META_WA_PHONE_ID |
| Resend (email) | Email in 5 minutes | Free Resend account and API key. Until you verify a domain it can only send to your own address. | VP_EMAIL_PROVIDER=resend RESEND_API_KEY |
During demos set VP_DEMO_PHONE and VP_DEMO_EMAIL to your own number and email so every confirmation comes to you. Then run ./start.sh. For a real client, WhatsApp needs a verified WhatsApp Business account (through Twilio, Meta or 360dialog) with approved message templates.
| What | Where |
|---|---|
| n8n workflow, Executions tab = every webhook | http://localhost:5678 |
| Tool webhook | /webhook/6b375bba-…/voicepilot/tool/:name |
| Post-call webhook | /webhook/voicepilot/post-call?key=… |
| KPI feed (Report page) | /webhook/voicepilot/kpi?key=…&days=30 |
| Live data feed (Live data page) | /webhook/voicepilot/data?key=… |
| Postgres (TablePlus, DBeaver, psql) | localhost:5433 · db voicepilot · owner credentials in db/.env |
| Useful views | v_transcripts, v_open_cases, v_daily, v_intents, v_tool_daily |
| ElevenLabs agent | elevenlabs.io/app/agents → "VoicePilot | Layla" |
| Symptom | Fix |
|---|---|
| Layla says the system is unavailable | Postgres is down. Run ./start.sh. |
| Layla's tools fail or time out | The tunnel changed or stopped. Run ./start.sh; it re-points Layla automatically. |
| Conversations never show up | Check the post-call webhook in ElevenLabs → Settings → Webhooks (it must be enabled), then n8n → Executions. |
| Report says "could not load" | n8n isn't running, or config.js is missing. Run ./start.sh. |
| "lookup_locked" in Live data | Three wrong name or unit attempts. Start a new conversation. |
| Chat ends right away | A guardrail fired, probably prompt injection. Start a new chat. |