Replaces fragile whole-screen text matching with a screenshot-diff step that
finds each dialog's actual pixel bounds, then restricts every subsequent AI
locate/click to that cropped region — eliminating false matches from text
elsewhere on screen (title bars, side panels). Adds column-boundary and
row-height detection so the patient row and Exam procedure click positions
are computed geometrically instead of relying on repeated fuzzy AI guesses
for visually similar neighbors. Also adds per-run screenshot/debug-crop
backups and a structured run.log for diagnosing failed runs, plus a
cmd:move primitive on the Windows agent for pre-click confirmation crops.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a Windows-side agent (screenshot/click/type over HTTP) plus backend
services to locate UI elements via vision and drive an existing-patient
appointment flow in Open Dental, wired into the Copy/Type Agent page and
socket progress updates.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Chatbot: submitting with only a screenshot attached (no text) now triggers the same
"AI Detect & Check Eligibility" flow as the Copy Agent page
- detect-eligibility-info now also extracts the visible insurance payer name and picks
Tufts SCO auto-check when both Sun Life and DentaQuest are detected, else MassHealth
- Chatbot-attached files are now handed off to both claim and preauth forms unconditionally,
not just claims
- TuftsSCO preauth Selenium worker: verify-and-retry the Tooth field (typing was silently
getting reset by duplicate-procedure-code warning banners), add a final re-verification
pass across all rows, fix acknowledgement-checkbox targeting/verification, and verify
the "Next step" click actually advances the wizard instead of trusting a blind click
- selenium-claim route no longer re-appends previously saved appointment
attachments when the chatbot's autoSubmit flow already sent every intended
file, preventing an unwanted extra upload (e.g. attach 2 -> Selenium sees 3).
- Add a "Take screenshot" button to the AI chatbot's attachment bar, reusing
the same capture/crop flow as the appointment editor, so claims and
preauths can be submitted from the chatbot with a live screenshot attached.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Add "surg ext" alias mapping to D7210 in the AI CDT lookup.
- Add GET/DELETE endpoints for appointment attachments, and load existing
attachments in the appointment edit modal (they were uploaded correctly
but never re-fetched, so they appeared to vanish on reopen).
- Fix claim-form.tsx so previously-saved appointment attachments are always
loaded (even when a claim already exists) instead of only when creating a
brand-new claim, preventing them from being silently wiped on save.
- Merge existing appointment attachments into every insurance submit handler
(MassHealth, CCA, DDMA, UnitedDH, TuftsSCO) so Selenium submission always
includes files saved earlier via the Schedule editor, not just files
uploaded in the current claim-form session.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds an optional Provider (NPI) select to the Edit Appointment form,
sourced from Settings > NPI Providers. The AI "Claim for Column" batch
flow now prefers the appointment's own provider when resolving which
provider Selenium should select on the insurance site (e.g. MassHealth
rendering provider dropdown), falling back to the existing per-procedure
selection, active claim, or first configured provider.
Also resyncs packages/db's committed compiled schema/client artifacts
with their .ts sources — they had drifted out of sync (some untouched
since July), which was silently stripping the new npiProviderId field
via stale .strict() zod schemas.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets staff attach files to an appointment directly from the edit form via
local disk upload, picking a previously-saved scanned attachment, or an
inline screenshot with crop-before-save (matching the Copy Agent page's
crop UX), instead of detouring through the Copy Agent page. Also fixes a
latent data-loss bug in claim-form's save-for-appointment call, which
replaced all AppointmentFile rows instead of merging with existing ones.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- AI claim queue now uses previously saved AppointmentProcedure data (with a derived insurance site key) when an appointment has no notes but has saved procedures, instead of skipping with "No notes on this appointment"
- selenium-claim route now reads saved AppointmentFile attachments from disk when they weren't re-uploaded in the browser, so images/PDFs already attached via "Select Procedures" reach the MassHealth submission
Adds an "AI Detect & Check Eligibility" action to the Copy Agent page that
sends captured screenshots to Claude to extract Member ID/DOB, then opens
the chatbot's eligibility confirm step pre-filled with the result.
- POST /api/ai/detect-eligibility-info: Claude vision extraction of
Member ID/DOB from uploaded screenshots.
- POST /api/patients/create-temp: auto-creates a placeholder "PID-<id>"
patient (matched by insuranceId+dob) when no patient is selected, so
screenshots can be saved immediately; the real eligibility check later
rewrites the patient's name and Cloud Storage folder automatically via
the existing createOrUpdatePatientByInsuranceId matching logic.
- Chatbot: Member ID/DOB on the confirm card are now editable (MM/DD/YYYY),
with edits synced back to the patient record before the check runs.
- Fixed a useEffect ordering bug on the insurance-status page that could
silently clear a chatbot-prefilled Member ID/DOB before the auto-trigger
effect ran, preventing the eligibility check from ever starting.
- Added a local-disk-only screenshot backup (apps/Backend/uploads/
screenshot-backups/) for debugging, not exposed anywhere in the UI.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Native file inputs can't be pointed at a default server folder for security
reasons, so instead the Browse buttons on the Claim/PreAuth/Select Procedures
forms and the AI chatbot's attach button now also offer picking from
previously saved Cloud Storage attachments (e.g. Copy Agent screenshots) via
a new server-side picker modal, scoped per-patient on the claim forms and
unscoped (recent across all patients) in the chatbot.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
filesForQueue was collected from AppointmentFile/ClaimFile records but only
wired into the MH and CCA job payloads. The other three insurers' Selenium
workers read claim.claimFiles and always got an empty list, so attachments
already saved to Cloud Storage silently never got attached to the claim.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- PDF viewer hung on "Loading" forever for cloud-backed PDFs: streamFileTo
pipes with {end: false} (by design, so cloud-storage.ts's own routes can
call res.end() themselves), but the new /pdf-files/:id cloud branch never
did, so the HTTP response never completed.
- Documents page showed nothing for patients whose PDFs are only in Cloud
Storage: the page lists categories from the legacy PdfGroup table, which
new saves stopped populating. savePdfToCloudStorage now also ensures a
blob-less PdfGroup row exists per patient/category so the category still
appears, with actual files served from Cloud Storage via the existing
merge in the pdf-files/group listing routes.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New saves go into the patient's Cloud Storage folder under category subfolders
(Eligibility/Claims/PreAuth/Claim Status/Attachments) via a new
storage.savePdfToCloudStorage helper, instead of the legacy PdfGroup/PdfFile
Postgres blob tables. Old records and their read paths are left untouched for
backward compatibility; PDF list/content/delete endpoints and viewers now
transparently handle both legacy numeric ids and new "cloud:<id>" ids.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Renames the sidebar entry to "AI Copy/Type Agent" with Copy Agent, Type
Agent, and Settings sub-pages. Settings shows the server's LAN IP
address (via a new /api/ai/network-info endpoint) since it changes
depending on which office the app is running in.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claim attachments were being appended without checking for existing
duplicates, causing files (e.g. the same PDF) to be sent to the
insurance clearinghouse multiple times.
Adds a new Settings → Advanced → Selenium Settings page where the office
name (paymentGroupId) used in United/DentalHub portal dropdowns can be
configured without touching code. Previously hardcoded as "Summit Dental
Care"; now injected from DB into the three United workers at job dispatch.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New lab_rx_template table with sortOrder and Prisma migrations
- Backend CRUD + reorder routes at /api/lab-rx/templates
- Auto-seeds 3 Highland Dental Studio defaults on first use per user
- LabRxModal: template picker, inline rename, up/down reorder, full
template form with sections (Template Name / Lab Info / Case Details)
- RX popup: Tooth Number, Shade, Due Date, Doctor (from NPI providers),
Instructions pre-filled with "Please make / Thank you!", print output
- lab-rx-templates.ts config file for future git-managed additions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Make appointmentId nullable/optional in Prisma Zod schema via @zod rich
comment so claims can be submitted without an existing appointment
- Convert undefined appointmentId to null in all claim form handlers and
the backend claim creation endpoint
- Add AI classifier rule for expanding one procedure across multiple
comma-separated tooth numbers (e.g. "post/core on #23, 24, 25, 26")
- Add "post/core" (slash) alias to CDT lookup maps
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add "comp" as alias for composite in CDT lookup. Update classifier
prompt with explicit examples for "claim comp #8 ml for lisa today"
and dental surface letter definitions (M/D/L/F/B/O/V/I) so the LLM
correctly treats #tooth+surfaces as composite fillings, not insurance.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a toggle behind the Go button on the Payments page to automatically run the MH batch payment check on a user-selected day of week and hour. Default is off.
- Schema: added autoMhCheckEnabled, autoMhCheckDayOfWeek, autoMhCheckHour to User model
- Backend: new mhBatchPaymentService (shared logic), GET/PUT /auto-mh-check-setting routes, hourly cron job that fires on matching day+hour
- Frontend: toggle + day select (Mon–Sun) + time select (hourly) that persist immediately to DB
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- PDF import now marks payments as PAID when MassHealth patient's
mhPaidAmount >= totalBilled (no patient balance)
- Newly created patients from MH vouchers get insuranceProvider = 'MassHealth'
- Existing patients with blank insuranceProvider get it filled on import
- Fix: update patient name from PDF if existing record has empty name
- Various frontend/selenium/route updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
applyMissingMigrations ran the entire migration SQL as one transaction,
which rolled back all changes when any statement failed (e.g. adding a
NOT NULL column to a non-empty table). Replaced with prisma db push
which compares the schema and applies only what's needed. Also added
support for importing .sql files (not just .zip) in auto-import.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds autoImportEnabled/autoImportHour to rclone config. A separate
hourly cron finds the latest .zip in backups/ and restores it to the
database (drop schema, psql restore, apply migrations). Frontend shows
toggle + time picker + Import Now button in the Receiver PC section.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace hardcoded 8 PM / 9 PM backup schedules with user-selectable
hour dropdowns. Adds autoBackupHour and usbBackupHour fields to User
model. Cron jobs now check every hour against the configured time.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Source PC serves backups/ folder via rclone WebDAV server (auto-starts with app).
Receiver PC pulls backups on schedule using rclone sync.
Network Backup UI now has two tabs: Rclone and API Key.
Rclone installed automatically via postinstall script.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Requests from outside 10.x, 192.168.x, 172.16-31.x, 127.x are rejected
with 403 before the API key is even checked. This prevents the database
dump endpoint from being reachable from the internet even if the key leaked.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The /network-backup and /network-backup-files routes use their own
X-Network-Backup-Key authentication. Mounting them behind authenticateJWT
blocked all receiver sync requests before they could be validated.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copy-paste often adds trailing whitespace that breaks the API key comparison.
Updated placeholder and description to show the correct URL format (no port number).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chat history was stored only in sessionStorage, causing member ID and DOB
to be lost when the session expired or corrupted — requiring logout/login.
Now history is saved to a per-user JSON file on the backend and loaded on
mount, so the LLM always has full conversation context.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add public /api/greeting endpoint that generates a daily AI greeting (cached per day)
- Replace static hero text with dynamic AI greeting (ChatGPT/Claude style)
- Add Stripe-style animated gradient blobs with 14 daily-rotating color palettes
- Use Plus Jakarta Sans font and Sparkles icon for modern AI look
- Update subtitle to "Driven by multiple AI agents"
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Pre-auth step9: scan all table cells alphanumeric-first (A0260616190876 format),
URL extraction, body scan fallback with debug logging
- Pre-auth route: save service lines (totalBilled) when creating PREAUTH claim record
so claim page shows correct billed amount after selecting patient
- Pre-auth processor: read pdf_path fallback alongside pdf_url from Selenium result
- UnitedDH/SCO workers: billing entity selection via direct paymentGroupId click,
Summit Dental Care first with fallback, Escape to close dropdown
- Pre-auth form: remove Other Insurance step (not present on pre-auth page),
file upload direct to hidden input without button click
- Pre-auth step8: JS click + Submit Authorization in XPath, Continue via [last()] + JS click
- RCT combo buttons added (pre-auth form only): RCT Ant/Post/Crown, PreM/Post/Crown,
Mol/Post/Crown; claim form excludes these three combos
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Frontend: upload attachments to disk before sending pre-auth payload (same pattern as claims)
- cloud-storage: finalizeFileUpload returns diskPath so Python workers get real file paths
- upload-to-cloud route: return diskPath instead of API URL
- TuftsSCO preAuth worker: skip 'Add a file' button click; send_keys directly to hidden react-aria-Input
- TuftsSCO preAuth worker: JS focus() on tooth field to bypass warning-banner overlay
- TuftsSCO preAuth worker: 1.5s wait after procedure code for layout shift to settle
- TuftsSCO preAuth worker: step8 waits for 'thank' in page_source then extracts via 'submitted pre-authorization' regex
- helpers_tuftssco_preauth: convert pdf_path → pdf_url (http://localhost:5002/downloads/...)
- tuftsSCOPreAuthProcessor: use pdf_url (not pdf_path), save preAuthNumber to preAuthNumber field
- unitedDHPreAuthProcessor: save preAuthNumber to preAuthNumber field (not claimNumber)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- parseSrpCode: recognize "D4341 UL" / "4341 LR" etc., store quadrant in quad field
- matchOne: auto-prefix D for 4-digit inputs like "0120" → D0120
- LLM prompt: keep SRP code and quadrant together as one procedureName entry
- CdtMatch / CdtResult: add quad field, thread through matchedCodes action data
- claim-form.tsx: include quad in chatbot_claim_prefill type and spread to service line
- selenium_claimSubmitWorker.py: pass quad to fill_service_line, select quadrant
dropdown by index (UR=1, UL=2, LL=3, LR=4) matching MassHealth form structure
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass the user's primary NPI provider name through the eligibility and
claim routes so the Selenium workers click the matching option in the
DDMA member-search provider dropdown (data-testid=member-search_provider_select-btn)
rather than always falling back to the first entry.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- AI chat extracts 'with provider <name>' and routes claim to that provider
- Claim form reads provider from sessionStorage before any async effects run,
preventing saved claim/procedure data from overriding the chatbot selection
- NPI provider settings table shows Provider #1 / #2 labels with up/down
reorder buttons; Provider #1 is always the default for claims
- Default provider now uses sortOrder instead of hardcoded 'Mary Scannell'
- Added sortOrder column to NpiProvider schema with migration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Claim file uploads (chatbot or manual) now save to both the Cloud
Storage patient folder and the Documents page via new
POST /api/claims/upload-to-cloud endpoint
- MH submit flow now calls uploadAttachmentsToLocalFolder (same as
DDMA/United/Tufts) so chatbot-attached X-rays are persisted
- Removed old /upload-attachments disk route and attachmentDiskStorage
multer config; deleted uploads/patients/ folder
- uploadAttachmentsToLocalFolder now points to /upload-to-cloud and
sends patientId so the backend can create the patient folder
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add "Eligibility & Appointment" button to chatbot eligibility-id-ready card
- For known patients: creates today's appointment immediately, then opens eligibility page
- For unknown patients: navigates to eligibility page; after Selenium creates the patient,
auto-creates appointment via tryAppointmentFromChatbot on the insurance-status page
- Update MH Eligibility & Appointment button to create a today's schedule slot instead of
navigating to the appointments page; shows PDF preview on completion
- createAppointmentToday falls back from Column A to Column B when Column A is full;
returns column label in response so UI can display it
- Set AI-scheduled appointment duration to 15 minutes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Daily sync and Sync Now both pull database + uploads in one operation.
PC1 streams uploads/ as a zip via GET /network-backup-files (archiver).
PC2 clears cloud-storage, patients, and patient-documents then extracts
the fresh copy before resolving. Timeout extended to 5 min for large files.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PC2 can now automatically pull and restore a fresh copy of PC1's database
on a daily schedule. Config and API key are stored in local JSON files so
they survive database restores.
- New networkSyncConfigService: file-based config (network-backup-key.json,
network-sync-config.json) that persists through DB restores
- New networkSyncService: streams live pg_dump from source PC over HTTP and
pipes into psql, then reconnects Prisma and applies missing migrations
- 6 new endpoints: get/regenerate API key, serve backup stream (key-auth
only), get/save sync config, trigger immediate sync
- Hourly cron job that fires only when current hour matches configured syncHour
- NetworkBackupManager component: shows this machine's key (show/copy/regen)
and receiver config (enable toggle, hour picker 0-23, source URL + key,
Save + Sync Now, last sync status)
- README setup guide for both PCs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Claim flow: show green confirm card (patient, CDT codes, service date) before Selenium starts
- CDT lookup: add DIRECT_CODE_MAP + ALIAS_MAP with 60+ dental abbreviations from office fee schedule
(2BW→D0272, 4BW→D0274, PA→D0220, FL→D1208, RCT codes, composite tooth#/surface parser, etc.)
- Composite filling parser: auto-map "#29 OB" → D2392 based on tooth# (front/back) and surface count
- Ask-and-learn: unknown CDT terms block claim and ask user; answer saved to DB alias map for future use
- Cancel on confirm card returns to chat (not full reset) so user can correct and retry
- Eligibility auto-trigger fix: reset autoTriggeredRef when autoTrigger resets to false so second
chatbot eligibility check on same page visit fires correctly (all 5 provider buttons fixed)
- check_eligibility by name now returns eligibility_id_ready with correct siteKey for non-MH patients
- DDMA/CCA/United/Tufts fee schedules updated with office prices (single Price field, no age split)
- getCodeMap case-insensitive matching fix (ddma/cca/etc. now correctly selected)
- Family plan member disambiguation: insuranceId+DOB composite lookup prevents overwriting siblings
- AI chat date fix: send clientDate from browser to avoid UTC midnight rollover (EST→wrong day)
- AI prompt: appointmentDate extracted for claim_only intent when user says "today" or a date
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add llm-factory.ts: unified LLM provider abstraction (Google/Claude/OpenAI)
- Install @langchain/anthropic and @langchain/openai packages
- resolveAiProvider picks active provider from DB settings (Claude > OpenAI > Google)
- All AI graphs (reminder, new-patient, reschedule, internal-chat) now accept provider+model params
- Add claudeAiModel, openAiModel, googleAiModel columns to ai_settings table
- New PUT /api/ai/provider-model route to save selected model per provider
- UI model dropdowns for Claude (Haiku/Sonnet/Opus), OpenAI (GPT-5.x series), Google (Gemini 2.5/3.x)
- Google AI section also gets model selector alongside existing API key field
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Documents page shows a "Local Folder" card for each selected patient
with an "Open in Cloud Storage" button that deep-links to their folder
- Cloud Storage page reads ?folderId URL param on mount and auto-opens
the folder panel for seamless navigation from Documents
- Backend: GET /api/cloud-storage/patient-folder/:patientId endpoint
that idempotently gets or creates a top-level CloudFolder per patient
- CloudFolder schema gains optional patientId field linked to Patient
- Disk directories for cloud storage folders now use the folder's name
(e.g. "Xiaohui Wang/") instead of the opaque "folder-{id}/" path
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add OpenAI, Claude AI, and DentalManagement AI sections to the AI API
Setting page, each with a masked API key input and an on/off toggle
(defaulting to off). Rename sidebar label from "Google AI Settings" to
"AI API Setting". Add provider-key and provider-enabled backend endpoints
and extend the AiSettings schema with 6 new fields.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Internal AI chat: schedule_appointment intent books earliest available
slot in Column A using office hours; claim_only intent looks up latest
past appointment for service date, asks user when two appointments are
within 7 days, auto-triggers correct Selenium worker with mapped prices
- Gemini model updated to gemini-flash-latest; conversation history (15
messages) passed for pronoun/reference resolution; history trimmed to
start with user turn so Gemini doesn't reject the context
- Insurance alias file (insuranceAliases.json) replaces hardcoded siteKey
matching; "tufs" now resolves to TUFTS_SCO
- DOB format normalized (MM/DD/YYYY → YYYY-MM-DD) before parseLocalDate;
autoCheck now fires for all insurance types, not just MH/CMSP
- Claim form auto-submit: all handlers (MH, CCA, DDMA, UnitedDH, Tufts)
accept formToUse and receive fee-schedule-priced form; prefillDone set
after chatbot code prefill so autoSubmit gate opens correctly
- Chatbot: chat history persisted in sessionStorage, cleared on logout
and auto-logout; Clear button writes fresh state synchronously; message
history window increased to 15
- DentaQuest/TuftsSCO Selenium: "Remember me" checkbox clicked before
sign-in to persist OTP trust cookie across sessions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add eligibility_by_id and check_and_claim intents to internal chat
- New cdt-lookup.ts: keyword search against fee schedule JSON (no LLM)
- New internal-chat-workflow.ts: deterministic orchestration — patient
resolution, insurance siteKey derivation, CDT code mapping
- Custom CDT aliases stored per-user in DB (TwilioSettings JSON blob)
with GET/PUT /api/ai/cdt-aliases endpoints
- Chatbot UI: new steps for eligibility-id-ready, check-and-claim-ready,
and need-insurance-clarification with insurance picker
- Settings UI: CDT Aliases CRUD table with built-in alias reference
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New DialPad component on Patient Connection page: clickable keypad,
call/hangup/mute buttons, duration timer, keyboard input support
- Backend: POST /api/twilio/voice-token issues Access Token for browser
Device; POST /api/twilio/webhook/voice-browser is the TwiML webhook
Twilio calls to bridge the browser to the patient's phone
- TwiML App SID field added to Twilio Settings (stored in templates JSON)
- README: one-time Twilio Console setup instructions for the dial pad
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>