From d5e4e398a76a9bb038054b34d5251ec7447f26f8 Mon Sep 17 00:00:00 2001 From: Kush Date: Thu, 23 Jul 2026 16:59:27 -0400 Subject: [PATCH] Rebrand to Migration Toolkit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the repo/package bw-voice-adapter → migration-toolkit and rebrand the three tools around the migration story: - preflight → Compatibility Check (src/compatibility-check, npm run compatibility-check) - the adapter → Real-Time Translator - generate → BXML Generator (src/bxml-generator, npm run bxml-generator) Rebuilds the README around the three-step flow, rebrands the web playground to "Migration Compatibility Check" (copy only; internal identifiers kept), and updates the Backstage catalog files, AGENTS.md, docs/demo.md, and the errors doc URL. Directory moves use git mv so history follows. Typecheck clean; 303/303 tests pass; playground builds. --- .bandwidth/catalog-info.yaml | 6 +- .bandwidth/component.yaml | 6 +- AGENTS.md | 10 +- CONTRIBUTING.md | 4 +- README.md | 172 +++++++++++------- docs/demo.md | 12 +- package-lock.json | 4 +- package.json | 6 +- scripts/build-playground.ts | 2 +- src/{generate => bxml-generator}/cli.ts | 2 +- src/{generate => bxml-generator}/generate.ts | 0 src/{generate => bxml-generator}/ingest.ts | 2 +- src/{generate => bxml-generator}/report.ts | 2 +- .../analyze.ts | 0 src/{preflight => compatibility-check}/cli.ts | 2 +- .../report.ts | 4 +- src/server/app.ts | 4 +- src/server/capture.ts | 4 +- src/server/errors.ts | 2 +- ...> bxml-generator-band-conformance.test.ts} | 4 +- ...enerate.test.ts => bxml-generator.test.ts} | 6 +- ...ht.test.ts => compatibility-check.test.ts} | 6 +- web/README.md | 6 +- web/app.ts | 2 +- web/index.html | 6 +- web/styles.css | 2 +- web/view-model.ts | 8 +- 27 files changed, 159 insertions(+), 125 deletions(-) rename src/{generate => bxml-generator}/cli.ts (96%) rename src/{generate => bxml-generator}/generate.ts (100%) rename src/{generate => bxml-generator}/ingest.ts (96%) rename src/{generate => bxml-generator}/report.ts (97%) rename src/{preflight => compatibility-check}/analyze.ts (100%) rename src/{preflight => compatibility-check}/cli.ts (92%) rename src/{preflight => compatibility-check}/report.ts (92%) rename test/{generate-band-conformance.test.ts => bxml-generator-band-conformance.test.ts} (92%) rename test/{generate.test.ts => bxml-generator.test.ts} (97%) rename test/{preflight.test.ts => compatibility-check.test.ts} (87%) diff --git a/.bandwidth/catalog-info.yaml b/.bandwidth/catalog-info.yaml index d5e70d7..c4013d3 100644 --- a/.bandwidth/catalog-info.yaml +++ b/.bandwidth/catalog-info.yaml @@ -2,10 +2,10 @@ apiVersion: backstage.io/v1alpha1 kind: Location metadata: schemaVersion: v1.0.0 - name: bw-voice-adapter-location - description: Links to additional entities in the bw-voice-adapter repository. + name: migration-toolkit-location + description: Links to additional entities in the migration-toolkit repository. annotations: - github.com/project-slug: Bandwidth/bw-voice-adapter + github.com/project-slug: Bandwidth/migration-toolkit spec: targets: - ./component.yaml diff --git a/.bandwidth/component.yaml b/.bandwidth/component.yaml index bd3c753..a92a5d3 100644 --- a/.bandwidth/component.yaml +++ b/.bandwidth/component.yaml @@ -2,10 +2,10 @@ apiVersion: backstage.io/v1alpha1 kind: Component metadata: schemaVersion: v1.0.0 - name: bw-voice-adapter - description: voice adapter + name: migration-toolkit + description: Developer Migration Toolkit — tools to ease Twilio→Bandwidth voice migration annotations: - github.com/project-slug: Bandwidth/bw-voice-adapter + github.com/project-slug: Bandwidth/migration-toolkit bandwidth.com/languages: Node.js organization: BW costCenter: diff --git a/AGENTS.md b/AGENTS.md index ef430fd..a3ebcf7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,7 +18,7 @@ An agent runs `band` to provision, then configures and runs this adapter. ## What an agent can and cannot do -**Can do unattended:** preflight analysis, provisioning on an *existing* +**Can do unattended:** compatibility-check analysis, provisioning on an *existing* Bandwidth account via `band`, adapter configuration, and the readiness check. **🧍 Human required** (flagged inline below): creating a brand-new Bandwidth @@ -27,11 +27,11 @@ host, and final by-ear call verification. ## Runbook -### Phase 1 — Preflight +### Phase 1 — Compatibility Check ```bash npm install -npm run preflight -- # complexity + per-file verdict -npm run generate -- # writes out/bxml/*, out/MIGRATION.md, out/coverage.json +npm run compatibility-check -- # complexity + per-file verdict +npm run bxml-generator -- # writes out/bxml/*, out/MIGRATION.md, out/coverage.json ``` Read `out/coverage.json` for the machine-readable verdict. If a source file uses the Twilio SDK to build TwiML at runtime, it is a **dynamic source** — there is no @@ -123,7 +123,7 @@ Translation is a fixed rulebook (`src/matrix/twilio-voice.json`), not a guess. there is no static markup to transpile. Run the adapter with `ADAPTER_CAPTURE_DIR=` and place a few test calls; each customer TwiML response is written there verbatim (content-addressed, deduped). Then - `npm run generate -- ` produces standalone BXML for the paths those + `npm run bxml-generator -- ` produces standalone BXML for the paths those calls exercised. Capture only covers exercised paths — branches you never dial won't appear, so drive every flow you care about (or port the rest by hand). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3b38bc5..cc21b96 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,10 +1,10 @@ # Contributing -Thanks for helping make the adapter better. Issues and PRs are the main way it improves, and they're genuinely welcome. +Thanks for helping make the Migration Toolkit better. Issues and PRs are the main way it improves, and they're genuinely welcome. ## How this project is supported -bw-voice-adapter is best-effort and community-supported — not a fully supported Bandwidth product. It's proven on real calls, but there's no team on it full-time, so response times vary and prioritization isn't guaranteed. If something's blocking you, the fastest path is usually a PR you drive yourself. For SLA-backed support on a production migration, talk to your Bandwidth account team. +The Migration Toolkit is best-effort and community-supported — not a fully supported Bandwidth product. It's proven on real calls, but there's no team on it full-time, so response times vary and prioritization isn't guaranteed. If something's blocking you, the fastest path is usually a PR you drive yourself. For SLA-backed support on a production migration, talk to your Bandwidth account team. ## How to report a problem diff --git a/README.md b/README.md index b801244..ec876db 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# bw-voice-adapter +# Migration Toolkit -**Run an existing Twilio voice app on Bandwidth's network — without rewriting it.** Point your app at the adapter, change one URL, and its calls now run on Bandwidth. The code never changes. +**Developer tools that cut the effort of migrating a voice app from Twilio to Bandwidth — in three steps.** Check what's compatible, prove it on a real call, and generate the BXML you'll ship. Steps 1 and 2 need no code changes at all; step 3 hands you usable BXML. -> **Status:** Inbound/outbound calls, call control, recordings, and the two key webhooks are working and proven on real calls, with an automated test suite and clean typecheck. This adapter does not order or manage Bandwidth numbers itself — pair it with the [`band` CLI](#pairs-with-the-band-cli) for account-side provisioning. +> **Status:** The everyday call-flow building blocks — inbound/outbound calls, call control, recordings, and the two key webhooks — are proven on real Twilio↔Bandwidth calls, with an automated test suite and clean typecheck. This toolkit does not order or manage Bandwidth numbers itself — pair it with the [`band` CLI](#pairs-with-the-band-cli) for account-side provisioning. --- @@ -13,79 +13,68 @@ A phone call on Twilio or Bandwidth is driven by a little script the network rea - **Twilio** writes that script in its language, **TwiML**. - **Bandwidth** uses **BXML** for the same ideas. -Same concepts, different words — like British vs. American English. This adapter is a **live translator** in the middle: a call comes in, Bandwidth asks the adapter "what do I do?", the adapter asks the customer's *unchanged* Twilio app the same question, gets TwiML back, translates it to BXML on the spot, and hands it over. The customer points one URL at the adapter; nothing else changes. +Same concepts, different words — like British vs. American English. Roughly 80% maps one-to-one. Moving a voice app from Twilio to Bandwidth normally means rewriting it against a different API; this toolkit turns most of that rewrite into a config change and a copy/paste, and tells you — deterministically, never by guessing — exactly where the real work is. -> Moving a voice app from Twilio to Bandwidth normally means rewriting it against a different API. This turns that rewrite into a one-line config change — the app keeps running while its calls (and economics) move to Bandwidth. +--- -The translation is a fixed rulebook driven by a single [compatibility matrix](src/matrix/twilio-voice.json), not an AI guessing — for live calls, "mostly right" isn't good enough. When a customer uses something the adapter *can't* do yet, it **says so and stops**, rather than breaking the call silently. +## The three steps -### Pairs with the `band` CLI +### 1. Compatibility Check -This adapter only translates the **call flow** — it does not touch a customer's -Bandwidth account. Account-side actions (buying a number, creating a Voice -Application, activating voice on a number) are a separate concern, handled by -the [`band` CLI](https://dev.bandwidth.com/tools/cli/). The two pair naturally: an -agent runs `band` to provision the account, then configures and runs this -adapter to carry the calls. If you're an agent driving an end-to-end cutover, -start with **[`AGENTS.md`](AGENTS.md)** — it's the phased runbook with the -exact `band` commands, the env vars this adapter reads, and every step that -still needs a human. +**What it does:** Scans a customer's existing codebase and checks how their TwiML and Twilio SDK usage maps to Bandwidth's BXML. ---- +**Outcome:** Size the migration effort and identify blockers **without writing a line of code**. You get a migration-complexity score and a per-feature *works-as-is / heads-up / blocker* breakdown. -## Capabilities +```bash +npm install +npm run compatibility-check -- examples/full-pv-app # or a path to your own app +``` -**Verified:** `Live` = proven against real Twilio↔Bandwidth calls · `Unit` = automated tests · `—` = not yet / not supported. +### 2. Real-Time Translator -### TwiML verbs -| Verb | Status | Verified | -|---|:--:|:--:| -| Say, Play, Pause, Hangup, Redirect | ✅ | Live | -| Gather (DTMF + speech) | ✅ | Live | -| Record (+ `recordingStatusCallback`) | ✅ | Live | -| Dial → Number / SIP | ✅ | Live | -| Dial → Conference (mute + events; no hold music / lifecycle) | ⚠️ | Live | -| Connect/Start/Stop Stream, Start/Stop Transcription | ✅ | Unit | -| Reject, Refer (SIP) | ⚠️ | Unit | -| Queue, Client, Enqueue, Leave, Pay | ❌ | — (no BW primitive — fail loudly) | +**What it does:** Completes a *real* Bandwidth call on the customer's unchanged Twilio application, translating between TwiML and BXML live, step by step. Any feature that can't be translated is surfaced **audibly in the call and in the error logs** — it fails loudly, never silently. -### REST facade & callbacks -| Capability | Status | Verified | -|---|:--:|:--:| -| Originate / hang up / redirect / fetch call | ✅ | Live | -| List / fetch / download recordings; pause-resume | ✅ | Live/Unit | -| Status callback (call completion) | ✅ | Unit | -| Recording callback (`recordingStatusCallback`) | ✅ | Unit | -| Media Streams bridge (AI-voice path) | ✅ | Unit | +**Outcome:** See how well the Twilio app actually works on Bandwidth today by placing a test call and getting deterministic results — no rewrite, one URL change. As it runs, the raw TwiML the call flow exercised is captured and turned into standalone TwiML, ready to feed step 3. -Number provisioning (search/order/activate) isn't part of this adapter — it's -handled by the [`band` CLI](#pairs-with-the-band-cli); see the Phase 2 runbook -in [`AGENTS.md`](AGENTS.md). +```bash +npm start # translator listens on :3000 — point your app's one URL at it +``` + +The translation is a fixed rulebook driven by a single [compatibility matrix](src/matrix/twilio-voice.json), not an AI guessing — for live calls, "mostly right" isn't good enough. + +### 3. BXML Generator + +**What it does:** Copy/paste the raw TwiML from step 2 (or any TwiML you have) and it generates the BXML for your Bandwidth migration. + +**Outcome:** Cut dev work in half with usable BXML that's ready to integrate with your application. + +```bash +npm run bxml-generator -- # writes out/bxml/*, out/MIGRATION.md, out/coverage.json +``` --- -## Quickstart +## Try the Compatibility Check in your browser — no command needed + +The **Migration Compatibility Check** is a single, self-contained HTML file a sales engineer can double-click and present to a prospect. Paste a customer's TwiML (or pick a curated example) and it shows — live — a migration-complexity score, a *works-as-is / heads-up / blocker* verdict, the translated BXML, and a forwardable report. -**Migration Preflight playground (for demos — a double-click HTML file):** ```bash npm install npm run playground:build # writes dist/playground.html open dist/playground.html # (macOS) or just double-click it ``` -A single self-contained page — no server, no network, works offline. Paste a customer's TwiML (or pick a curated example) and see the live *works-as-is / heads-up / blocker* verdict, a migration-complexity score, the translated BXML, and a forwardable report. It runs the **real** translation engine in the browser, so the verdict matches what the adapter does in production. Built for sales engineers to drive live on a screen-share; see [`web/README.md`](web/README.md). -**See a migration report (30s, no accounts):** -```bash -npm install -npm run preflight -- examples/full-pv-app -``` -Prints a migration-complexity score and a per-feature *works-as-is / heads-up / blocker* breakdown. +No server, no network, works offline. It runs the **real** translation engine in the browser, so the verdict matches what the Real-Time Translator does in production. See [`web/README.md`](web/README.md). + +--- + +## Quickstart -**Run the translation loop locally (no credentials):** see [`docs/demo.md`](docs/demo.md) §1–§2 — the customer's sample app + the adapter + simulated Bandwidth webhooks, all on localhost. §4 covers the recording/status callbacks. +**Run the translation loop locally (no credentials):** see [`docs/demo.md`](docs/demo.md) §1–§2 — the customer's sample app + the Real-Time Translator + simulated Bandwidth webhooks, all on localhost. §4 covers the recording/status callbacks. -**Run the adapter** (Node 20+): +**Run the Real-Time Translator** (Node 20+): ```bash -npm start # adapter on :3000 +npm start # translator on :3000 npm test # run the test suite npm run typecheck npm run doctor # readiness check — see AGENTS.md Phase 5 @@ -98,11 +87,11 @@ npm run doctor # readiness check — see AGENTS.md Phase 5 | `HOST` | Listen interface (default `127.0.0.1`); set `0.0.0.0` for containers/exposed deployments | | `EGRESS_ALLOW_PRIVATE` | Set `1` to allow outbound fetches to private/loopback ranges (local dev only) | | `EGRESS_ALLOW_HOSTS` | Optional comma-separated host allowlist; when set, outbound fetches are restricted to exactly these hosts (default-deny) | -| `PUBLIC_BASE_URL` | Public HTTPS base of this adapter | +| `PUBLIC_BASE_URL` | Public HTTPS base of the translator | | `CUSTOMER_VOICE_URL` | The customer's Twilio voice webhook (inbound calls) | | `BW_ACCOUNT_ID` / `BW_CLIENT_ID` / `BW_CLIENT_SECRET` / `BW_APPLICATION_ID` | Bandwidth credentials (OAuth2 client-credentials), provisioned via `band` — see [`AGENTS.md`](AGENTS.md) | | `BW_ENVIRONMENT` | Optional — `test` targets BW's test hosts; defaults to `prod` | -| `ADAPTER_CAPTURE_DIR` | Optional — dir to persist each customer TwiML response (verbatim, content-addressed) so `generate` can turn the paths a test call exercised into standalone BXML. Essential for SDK-built apps with no static TwiML to transpile. Off by default | +| `ADAPTER_CAPTURE_DIR` | Optional — dir to persist each customer TwiML response (verbatim, content-addressed) so the BXML Generator can turn the paths a test call exercised into standalone BXML. Essential for SDK-built apps with no static TwiML to transpile. Off by default | | `ADAPTER_LOG=1` | Optional — enable request logging | Run `npm run doctor` (or `GET /readyz?deep=1` once the server is up) to confirm @@ -111,6 +100,37 @@ real calls. --- +## Capabilities + +**Verified:** `Live` = proven against real Twilio↔Bandwidth calls · `Unit` = automated tests · `—` = not yet / not supported. + +### TwiML verbs +| Verb | Status | Verified | +|---|:--:|:--:| +| Say, Play, Pause, Hangup, Redirect | ✅ | Live | +| Gather (DTMF + speech) | ✅ | Live | +| Record (+ `recordingStatusCallback`) | ✅ | Live | +| Dial → Number / SIP | ✅ | Live | +| Dial → Conference (mute + events; no hold music / lifecycle) | ⚠️ | Live | +| Connect/Start/Stop Stream, Start/Stop Transcription | ✅ | Unit | +| Reject, Refer (SIP) | ⚠️ | Unit | +| Queue, Client, Enqueue, Leave, Pay | ❌ | — (no BW primitive — fail loudly) | + +### REST facade & callbacks +| Capability | Status | Verified | +|---|:--:|:--:| +| Originate / hang up / redirect / fetch call | ✅ | Live | +| List / fetch / download recordings; pause-resume | ✅ | Live/Unit | +| Status callback (call completion) | ✅ | Unit | +| Recording callback (`recordingStatusCallback`) | ✅ | Unit | +| Media Streams bridge (AI-voice path) | ✅ | Unit | + +Number provisioning (search/order/activate) isn't part of this toolkit — it's +handled by the [`band` CLI](#pairs-with-the-band-cli); see the Phase 2 runbook +in [`AGENTS.md`](AGENTS.md). + +--- + ## REST facade Backend calls the Twilio SDK makes are served in Twilio's shape (under `/2010-04-01/Accounts/{accountSid}`) and translated to Bandwidth over OAuth2 Bearer: @@ -125,17 +145,17 @@ Backend calls the Twilio SDK makes are served in Twilio's shape (under `/2010-04 | `GET /Recordings/{sid}.{mp3,wav}` | recording media URL | audio, stream-proxied from Bandwidth | | `POST /Calls/{sid}/Recordings/{recSid}.json` | `recordings(sid).update({status})` | pause / resume (`stopped` fails loudly — no BW REST stop) | -Unknown calls/recordings return Twilio's `20404`; bad credentials return `20003`. Call/recording state is in-memory (single-instance): a recording must be listed before it can be fetched by SID on a fresh instance. Adapter-specific operational failures (missing params, internal errors) use a separate private code range — see [`AGENTS.md#errors`](AGENTS.md#errors). +Unknown calls/recordings return Twilio's `20404`; bad credentials return `20003`. Call/recording state is in-memory (single-instance): a recording must be listed before it can be fetched by SID on a fresh instance. Toolkit-specific operational failures (missing params, internal errors) use a separate private code range — see [`AGENTS.md#errors`](AGENTS.md#errors). This facade does **not** include number search/order/release — those routes were removed in favor of the `band` CLI (see [`AGENTS.md`](AGENTS.md) Phase 2). ### Callbacks to your app (egress) -The adapter also calls *you* back in Twilio's shape, mapped from Bandwidth's events and signed with `X-Twilio-Signature`: +The Real-Time Translator also calls *you* back in Twilio's shape, mapped from Bandwidth's events and signed with `X-Twilio-Signature`: - **Status callback** — when a call ends, the `StatusCallback` set on `calls.create()` gets a Twilio-shaped `completed` callback (`CallSid`, `CallStatus`, `CallDuration`), mapped from Bandwidth's disconnect event. -- **Recording callback** — a `` maps to Bandwidth's `recordingAvailableUrl`; when the recording is ready the adapter forwards a Twilio `recordingStatusCallback`, with `RecordingUrl` pointing back at this facade so a later fetch resolves through the adapter. +- **Recording callback** — a `` maps to Bandwidth's `recordingAvailableUrl`; when the recording is ready the translator forwards a Twilio `recordingStatusCallback`, with `RecordingUrl` pointing back at this facade so a later fetch resolves through it. --- @@ -145,26 +165,40 @@ The adapter also calls *you* back in Twilio's shape, mapped from Bandwidth's eve - **Conference hold music** — no Bandwidth equivalent. - **Speech recognition** — translated correctly, but must be enabled on the Bandwidth account. - **Stopping a recording via REST** — Bandwidth pauses/resumes over REST but has no REST *stop* (`StopRecording` is a BXML verb), so `Status=stopped` fails loudly rather than silently. -- **Number provisioning** — this adapter doesn't order, activate, or otherwise manage Bandwidth numbers; use the `band` CLI (see [`AGENTS.md`](AGENTS.md) Phase 2). +- **Number provisioning** — this toolkit doesn't order, activate, or otherwise manage Bandwidth numbers; use the `band` CLI (see [`AGENTS.md`](AGENTS.md) Phase 2). See [`AGENTS.md`](AGENTS.md) for the full unsupported/lossy verb breakdown and -the private error codes this adapter raises. +the private error codes the translator raises. + +--- + +## Pairs with the `band` CLI + +The Real-Time Translator only handles the **call flow** — it does not touch a +customer's Bandwidth account. Account-side actions (buying a number, creating a +Voice Application, activating voice on a number) are a separate concern, handled +by the [`band` CLI](https://dev.bandwidth.com/tools/cli/). The two pair +naturally: an agent runs `band` to provision the account, then configures and +runs the translator to carry the calls. If you're an agent driving an +end-to-end cutover, start with **[`AGENTS.md`](AGENTS.md)** — it's the phased +runbook with the exact `band` commands, the env vars the translator reads, and +every step that still needs a human. --- ## How it's built -All translation is driven by one declarative compatibility matrix (`src/matrix/twilio-voice.json`) — the runtime adapter, the pre-flight report, and the Migration Preflight playground all read the same data, so they can't disagree. +All translation is driven by one declarative compatibility matrix (`src/matrix/twilio-voice.json`) — the Real-Time Translator, the Compatibility Check report, and the Migration Compatibility Check webpage all read the same data, so they can't disagree. ``` -src/translator TwiML → BXML translation -src/twilio Twilio-shaped REST facade + signed webhooks (egress) -src/streams Media Streams bridge -src/server the proxy (Fastify) wiring it together, readiness check (/readyz, npm run doctor) -src/preflight static migration-complexity report -src/generate batch BXML generation + coverage report (see AGENTS.md Phase 1) -web Migration Preflight playground (double-click HTML demo) -scripts build-playground.ts + benches +src/translator TwiML → BXML translation +src/twilio Twilio-shaped REST facade + signed webhooks (egress) +src/streams Media Streams bridge +src/server the proxy (Fastify) wiring it together, readiness check (/readyz, npm run doctor) +src/compatibility-check static migration-complexity report (npm run compatibility-check) +src/bxml-generator batch BXML generation + coverage report (npm run bxml-generator; see AGENTS.md Phase 1) +web Migration Compatibility Check webpage (double-click HTML demo) +scripts build-playground.ts + benches ``` Tests live in `test/` (Vitest); CI gates `npm run typecheck` + `npm test`. diff --git a/docs/demo.md b/docs/demo.md index 8c2462e..d96ea5f 100644 --- a/docs/demo.md +++ b/docs/demo.md @@ -1,9 +1,9 @@ # Demo walkthrough -## 1. Pre-flight report (no credentials needed) +## 1. Compatibility Check (no credentials needed) ```bash -npm run preflight -- examples/sample-twilio-app +npm run compatibility-check -- examples/sample-twilio-app ``` Matrix-driven migration report for an unmodified Twilio app: complexity score, @@ -17,7 +17,7 @@ Terminal A — the "customer's" untouched Twilio app: cd examples/sample-twilio-app && npm install && npm start ``` -Terminal B — the adapter: +Terminal B — the translator: ```bash ADAPTER_ACCOUNT_SID=AC123 ADAPTER_AUTH_TOKEN=demo \ @@ -62,7 +62,7 @@ adapter URL (`/bw/initiate`), and a public HTTPS tunnel (ngrok) or small host. (A SIP peer is only needed on the legacy platform; the default Universal Platform path uses a VCP instead — see `AGENTS.md` Phase 2.) Call the BW number, walk the IVR by ear. Then exercise outbound via the REST facade with the real -`twilio` SDK pointed at the adapter base URL. +`twilio` SDK pointed at the translator base URL. P0 exit criteria for the live milestone: 1. Inbound IVR (Say/Gather/Transfer) works on a real phone call. @@ -79,7 +79,7 @@ CLI; see the Phase 2 runbook in [`AGENTS.md`](../AGENTS.md). Add a stand-in for the customer's callback receiver: ```bash -# Terminal D — prints whatever the adapter posts back +# Terminal D — prints whatever the translator posts back node -e "require('http').createServer((q,s)=>{let b='';q.on('data',d=>b+=d);q.on('end',()=>{console.log('\n['+q.url+'] '+b);s.end('ok')})}).listen(4001,()=>console.log('catcher :4001'))" ``` @@ -105,7 +105,7 @@ Expected (Terminal D): a Twilio `recordingStatusCallback` payload — ### 4b. Status callback (live call) The `StatusCallback` URL is captured on the outbound `calls.create` path, so this -is shown on a live call rather than locally. Point the `twilio` SDK at the adapter: +is shown on a live call rather than locally. Point the `twilio` SDK at the translator: ```js client.calls.create({ diff --git a/package-lock.json b/package-lock.json index ae21881..967feb7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "bw-voice-adapter", + "name": "migration-toolkit", "version": "0.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "bw-voice-adapter", + "name": "migration-toolkit", "version": "0.1.0", "dependencies": { "@fastify/formbody": "^8.0.2", diff --git a/package.json b/package.json index 47072fc..9a9e4fa 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "bw-voice-adapter", + "name": "migration-toolkit", "version": "0.1.0", "private": true, "type": "module", @@ -8,8 +8,8 @@ "typecheck": "tsc --noEmit", "start": "tsx src/server/index.ts", "doctor": "tsx src/server/doctor.ts", - "preflight": "tsx src/preflight/cli.ts", - "generate": "tsx src/generate/cli.ts", + "compatibility-check": "tsx src/compatibility-check/cli.ts", + "bxml-generator": "tsx src/bxml-generator/cli.ts", "bench": "tsx scripts/bench-translate.ts", "bench:overhead": "tsx scripts/bench-adapter-overhead.ts", "latency:report": "tsx scripts/latency-report.ts", diff --git a/scripts/build-playground.ts b/scripts/build-playground.ts index d1ef280..fa18498 100644 --- a/scripts/build-playground.ts +++ b/scripts/build-playground.ts @@ -1,4 +1,4 @@ -// Builds the self-contained Migration Preflight artifact: web/ sources + the +// Builds the self-contained Migration Compatibility Check artifact: web/ sources + the // real translation engine + DM Sans fonts, all inlined into a single // dist/playground.html a salesperson can double-click. No server, no network. diff --git a/src/generate/cli.ts b/src/bxml-generator/cli.ts similarity index 96% rename from src/generate/cli.ts rename to src/bxml-generator/cli.ts index d12d188..ab97df8 100644 --- a/src/generate/cli.ts +++ b/src/bxml-generator/cli.ts @@ -22,7 +22,7 @@ function walk(dir: string, out: string[] = []): string[] { const input = process.argv[2]; const outdir = process.argv[3]; if (!input || !outdir) { - console.error("Usage: npm run generate -- "); + console.error("Usage: npm run bxml-generator -- "); process.exit(2); } diff --git a/src/generate/generate.ts b/src/bxml-generator/generate.ts similarity index 100% rename from src/generate/generate.ts rename to src/bxml-generator/generate.ts diff --git a/src/generate/ingest.ts b/src/bxml-generator/ingest.ts similarity index 96% rename from src/generate/ingest.ts rename to src/bxml-generator/ingest.ts index 6e1229e..220225a 100644 --- a/src/generate/ingest.ts +++ b/src/bxml-generator/ingest.ts @@ -1,4 +1,4 @@ -import { analyzeSource } from "../preflight/analyze.js"; +import { analyzeSource } from "../compatibility-check/analyze.js"; // A single translatable TwiML document pulled out of the customer's app: // a standalone .xml file, a string embedded in source, or a TwiML diff --git a/src/generate/report.ts b/src/bxml-generator/report.ts similarity index 97% rename from src/generate/report.ts rename to src/bxml-generator/report.ts index 139e727..1c10725 100644 --- a/src/generate/report.ts +++ b/src/bxml-generator/report.ts @@ -13,7 +13,7 @@ function uniqueUrls(urls: PreservedUrl[]): PreservedUrl[] { return [...seen.values()]; } -/** Plain-English migration report, mirroring the pre-flight report's tone. */ +/** Plain-English migration report, mirroring the Compatibility Check report's tone. */ export function renderMigration(result: GenerateResult): string { const { docs, dynamicSources } = result; const clean = docs.filter((d) => d.findings.length === 0).length; diff --git a/src/preflight/analyze.ts b/src/compatibility-check/analyze.ts similarity index 100% rename from src/preflight/analyze.ts rename to src/compatibility-check/analyze.ts diff --git a/src/preflight/cli.ts b/src/compatibility-check/cli.ts similarity index 92% rename from src/preflight/cli.ts rename to src/compatibility-check/cli.ts index a620123..d6327df 100644 --- a/src/preflight/cli.ts +++ b/src/compatibility-check/cli.ts @@ -20,7 +20,7 @@ function walk(dir: string, out: string[] = []): string[] { const target = process.argv[2]; if (!target) { - console.error("Usage: npm run preflight -- "); + console.error("Usage: npm run compatibility-check -- "); process.exit(2); } diff --git a/src/preflight/report.ts b/src/compatibility-check/report.ts similarity index 92% rename from src/preflight/report.ts rename to src/compatibility-check/report.ts index 45df33e..e805d36 100644 --- a/src/preflight/report.ts +++ b/src/compatibility-check/report.ts @@ -11,7 +11,7 @@ export function renderReport(analyses: FileAnalysis[]): string { const relevant = analyses.filter((a) => a.verbs.length > 0); const score = complexityScore(relevant); const lines: string[] = [ - "# Twilio → Bandwidth migration pre-flight report", + "# Twilio → Bandwidth migration — Compatibility Check", "", `**Migration complexity: ${score}/10** (1 + warnings + 3×blockers, capped at 10)`, "", @@ -23,7 +23,7 @@ export function renderReport(analyses: FileAnalysis[]): string { const errors = a.findings.filter((f) => f.severity === "error"); const warnings = a.findings.filter((f) => f.severity === "warning"); if (errors.length === 0 && warnings.length === 0) - lines.push("✅ Runs through the adapter as-is.", ""); + lines.push("✅ Runs through the translator as-is.", ""); if (errors.length) { lines.push("### 🛑 Blockers", ""); for (const f of errors) diff --git a/src/server/app.ts b/src/server/app.ts index c37d7de..b6cf86e 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -37,7 +37,7 @@ export interface AdapterConfig { /** Basic-auth credentials Bandwidth presents on inbound webhooks (must match the app's CallbackCreds). */ webhookUser: string; webhookPassword: string; - /** Opt-in dir to persist each customer TwiML response for later `generate`. Undefined → no capture. */ + /** Opt-in dir to persist each customer TwiML response for the later BXML Generator. Undefined → no capture. */ captureDir?: string; } @@ -159,7 +159,7 @@ export function buildApp(config: AdapterConfig, deps: AdapterDeps): FastifyInsta throw err; } // Capture the raw customer TwiML (URLs verbatim, pre-rewrite) before we - // translate it — this is exactly what `generate` ingests to produce + // translate it — this is exactly what the BXML Generator ingests to produce // standalone BXML for the paths a test call exercised. if (config.captureDir) { try { diff --git a/src/server/capture.ts b/src/server/capture.ts index 9291480..2010bc5 100644 --- a/src/server/capture.ts +++ b/src/server/capture.ts @@ -4,14 +4,14 @@ import { join } from "node:path"; /** * Persist a raw TwiML response the adapter fetched from the customer app, so a - * later `npm run generate` over the capture dir can turn the paths a test call + * later `npm run bxml-generator` over the capture dir can turn the paths a test call * actually exercised into standalone BXML. * * The filename is a content hash — never request input — so identical responses * dedupe to one file and untrusted input never participates in filesystem * addressing (same guardrail as scripts/capture-server.mjs). We store the raw * TwiML verbatim (customer URLs intact, no proxy rewrite), which is exactly what - * `generate` ingests. + * the BXML Generator ingests. * * Written private-by-default (dir 0700, file 0600): raw TwiML can carry callback * URLs with embedded tokens, so captures are not world-readable. Synchronous I/O diff --git a/src/server/errors.ts b/src/server/errors.ts index c90739f..994af0d 100644 --- a/src/server/errors.ts +++ b/src/server/errors.ts @@ -11,7 +11,7 @@ export interface AdapterError { // Adapter-private code range. Deliberately NOT in Twilio's registry — reusing a // real Twilio code (e.g. 21610 = STOP/unsubscribed) with a twilio.com link would // misdiagnose an adapter failure. -const DOCS = "https://github.com/Bandwidth/bw-voice-adapter/blob/main/AGENTS.md#errors"; +const DOCS = "https://github.com/Bandwidth/migration-toolkit/blob/main/AGENTS.md#errors"; /** Operational errors the adapter itself raises (distinct from Twilio-API-compat errors). */ export const adapterErrors = { diff --git a/test/generate-band-conformance.test.ts b/test/bxml-generator-band-conformance.test.ts similarity index 92% rename from test/generate-band-conformance.test.ts rename to test/bxml-generator-band-conformance.test.ts index d8ffbcd..98c8949 100644 --- a/test/generate-band-conformance.test.ts +++ b/test/bxml-generator-band-conformance.test.ts @@ -3,8 +3,8 @@ import { execFileSync } from "node:child_process"; import { readdirSync, readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import { join } from "node:path"; -import { ingest } from "../src/generate/ingest.js"; -import { generateDocs } from "../src/generate/generate.js"; +import { ingest } from "../src/bxml-generator/ingest.js"; +import { generateDocs } from "../src/bxml-generator/generate.js"; // Conformance: generated standalone BXML must be accepted by Bandwidth's own // CLI (`band bxml raw`). Skips (does not fail) when `band` is not on PATH, so diff --git a/test/generate.test.ts b/test/bxml-generator.test.ts similarity index 97% rename from test/generate.test.ts rename to test/bxml-generator.test.ts index ec30bc0..f17e48f 100644 --- a/test/generate.test.ts +++ b/test/bxml-generator.test.ts @@ -1,7 +1,7 @@ import { describe, it, expect } from "vitest"; -import { ingest, extractTwimlBlocks } from "../src/generate/ingest.js"; -import { generateDoc, generateDocs, bxmlFileName } from "../src/generate/generate.js"; -import { renderMigration, buildCoverage, type GenerateResult } from "../src/generate/report.js"; +import { ingest, extractTwimlBlocks } from "../src/bxml-generator/ingest.js"; +import { generateDoc, generateDocs, bxmlFileName } from "../src/bxml-generator/generate.js"; +import { renderMigration, buildCoverage, type GenerateResult } from "../src/bxml-generator/report.js"; describe("ingest", () => { it("treats a standalone .xml TwiML file as one doc", () => { diff --git a/test/preflight.test.ts b/test/compatibility-check.test.ts similarity index 87% rename from test/preflight.test.ts rename to test/compatibility-check.test.ts index 20ed56f..31f70fe 100644 --- a/test/preflight.test.ts +++ b/test/compatibility-check.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from "vitest"; -import { analyzeSource } from "../src/preflight/analyze.js"; -import { renderReport, complexityScore } from "../src/preflight/report.js"; +import { analyzeSource } from "../src/compatibility-check/analyze.js"; +import { renderReport, complexityScore } from "../src/compatibility-check/report.js"; describe("analyzeSource", () => { it("detects verbs in embedded TwiML strings", () => { @@ -41,7 +41,7 @@ describe("report", () => { `hiq`, ); const md = renderReport([a]); - expect(md).toContain("# Twilio → Bandwidth migration pre-flight report"); + expect(md).toContain("# Twilio → Bandwidth migration — Compatibility Check"); expect(md).toContain("Enqueue"); expect(md).toMatch(/complexity/i); }); diff --git a/web/README.md b/web/README.md index 64c57f4..4cdcaff 100644 --- a/web/README.md +++ b/web/README.md @@ -1,4 +1,4 @@ -# Migration Preflight playground +# Migration Compatibility Check playground A single, self-contained HTML file a sales engineer can **double-click** and present to a prospect. Paste a customer's TwiML (or pick a curated example) and it shows — live — how much of that Twilio voice app runs on Bandwidth unchanged: a works-as-is / heads-up / blocker verdict, a migration-complexity score, the translated BXML, and a forwardable report. @@ -6,7 +6,7 @@ No server, no install, no network. It works offline on a plane. ## Why it's trustworthy -The page runs the **real translation engine** (`src/translator`, `src/preflight`) and the real compatibility matrix (`src/matrix/twilio-voice.json`) in the browser — the exact same code the live adapter uses. It does not reimplement any translation or scoring logic. Because the same matrix drives the adapter and this page, the verdict shown here provably matches what happens in production. That invariant is the point; don't break it by hand-coding results into the UI. +The page runs the **real translation engine** (`src/translator`, `src/compatibility-check`) and the real compatibility matrix (`src/matrix/twilio-voice.json`) in the browser — the exact same code the live adapter uses. It does not reimplement any translation or scoring logic. Because the same matrix drives the adapter and this page, the verdict shown here provably matches what happens in production. That invariant is the point; don't break it by hand-coding results into the UI. ## Build & run @@ -36,4 +36,4 @@ The visual design comes from a Bandwidth design-system mockup (DM Sans, primary ## Scope -Intentionally narrow: one screen, TwiML-document input only. It does **not** scan source code (that's the CLI `npm run preflight`), place live calls, or talk to Bandwidth. Those need a server and are out of scope for this demo tool. +Intentionally narrow: one screen, TwiML-document input only. It does **not** scan source code (that's the CLI `npm run compatibility-check`), place live calls, or talk to Bandwidth. Those need a server and are out of scope for this demo tool. diff --git a/web/app.ts b/web/app.ts index f56fa1b..2d4eb46 100644 --- a/web/app.ts +++ b/web/app.ts @@ -1,4 +1,4 @@ -// DOM wiring for the Migration Preflight page. Keeps no logic of its own beyond +// DOM wiring for the Migration Compatibility Check page. Keeps no logic of its own beyond // presentation — it calls buildView() (which runs the real engine) and paints // the result. See view-model.ts for the honesty invariant. diff --git a/web/index.html b/web/index.html index 5749bf3..ecf0d5c 100644 --- a/web/index.html +++ b/web/index.html @@ -3,7 +3,7 @@ - Twilio → Bandwidth Migration Preflight + Twilio → Bandwidth Migration Compatibility Check @@ -16,9 +16,9 @@ - Migration Preflight + Migration Compatibility Check -

Twilio → Bandwidth Migration Preflight

+

Twilio → Bandwidth Migration Compatibility Check

Paste the TwiML that drives your call flow and see, instantly, how much of it runs on Bandwidth's network unchanged — and exactly what needs attention. diff --git a/web/styles.css b/web/styles.css index 10827c6..25f3542 100644 --- a/web/styles.css +++ b/web/styles.css @@ -1,4 +1,4 @@ -/* Migration Preflight — styling. +/* Migration Compatibility Check — styling. * Brand tokens live in :root; swap them in one place to re-theme. * Design system: Bandwidth (primary #076ea8, DM Sans). @font-face rules for * DM Sans are injected by the build (base64) so the artifact stays self-contained. */ diff --git a/web/view-model.ts b/web/view-model.ts index b7bb49c..509e51b 100644 --- a/web/view-model.ts +++ b/web/view-model.ts @@ -1,4 +1,4 @@ -// Pure view model for the Migration Preflight page. +// Pure view model for the Migration Compatibility Check page. // // This is the ONLY new logic in the web tool, and it is deliberately thin: it // calls the real engine (analyzeSource / complexityScore / translateTwiml) and @@ -8,8 +8,8 @@ // page, the verdict shown here provably matches what the adapter does in // production. Keep this file DOM-free so it stays unit-testable. -import { analyzeSource } from "../src/preflight/analyze.js"; -import { complexityScore, renderReport } from "../src/preflight/report.js"; +import { analyzeSource } from "../src/compatibility-check/analyze.js"; +import { complexityScore, renderReport } from "../src/compatibility-check/report.js"; import { translateTwiml } from "../src/translator/translate.js"; import { loadMatrix } from "../src/matrix/load.js"; @@ -42,7 +42,7 @@ export interface PreflightView { headsUp: VerbCard[]; clean: VerbCard[]; bxml: string; - /** The same markdown migration report the CLI preflight produces (for "Copy report"). */ + /** The same markdown migration report the CLI Compatibility Check produces (for "Copy report"). */ reportMarkdown: string; }