npm package: @meetstream/migrate
Description: One-command migration from Recall.ai to MeetStream.ai — scans your codebase, maps APIs, transforms code.
Latest version: 2.0.0

# @meetstream/migrate

One-command migration from [Recall.ai](https://recall.ai) to [MeetStream.ai](https://meetstream.ai). Scans your codebase, maps every API call, shows the diff, and migrates.

## Why migrate?

- **~40% cheaper** — significantly lower per-hour bot and transcription costs
- **Agent-first architecture** — built for AI agents joining meetings, not just recording
- **Context layers** — real-time info fetching during calls
- **Migration CLI** — scan, preview, migrate, test in minutes

## Quick Start

No install needed — just run:

```bash
npx @meetstream/migrate scan ./your-recall-project
```

Or install globally:

```bash
npm install -g @meetstream/migrate
```

## Commands

### Scan (read-only)

Finds every Recall.ai reference in your codebase. **No files are modified.**

```bash
npx @meetstream/migrate scan ./your-project
```

### Dry Run (preview changes)

Shows exactly what would change — still no writes.

```bash
npx @meetstream/migrate --dry-run ./your-project
```

### Migrate

Applies all transformations. Creates a backup prompt before writing.

```bash
npx @meetstream/migrate --yes ./your-project
```

### Test APIs

Verifies your MeetStream API key works and endpoints are reachable.

```bash
npx @meetstream/migrate test --api-key YOUR_MEETSTREAM_API_KEY
```

## What It Detects

The scanner finds:

- **Base URLs** — `us-east-1.recall.ai`, `api.recall.ai`
- **API endpoints** — all 14 Recall endpoints mapped to MeetStream equivalents
- **Field names** — `meeting_url`, `recording_mode`, `metadata`, `transcription_options`, etc.
- **Webhook handlers** — `joining_call`, `in_call_recording`, `done`, `fatal`, etc.
- **SDK imports** — `require('recall-ai')`, `import recall`
- **Environment variables** — `RECALL_API_KEY`

Supported file types: `.js`, `.ts`, `.jsx`, `.tsx`, `.py`, `.go`, `.rb`, `.java`, `.env`, `.yaml`, `.json`

## Endpoint Mapping

| Recall.ai | MeetStream.ai | Notes |
|-----------|---------------|-------|
| `POST /api/v1/bot/` | `POST /api/v1/bots/create_bot` | Field names differ |
| `GET /api/v1/bot/{id}/` | `GET /api/v1/bots/{id}/detail` | |
| `POST /api/v1/bot/{id}/leave_call/` | `GET /api/v1/bots/{id}/remove_bot` | Method change |
| `DELETE /api/v1/bot/{id}/` | `DELETE /api/v1/bots/{id}/delete` | |
| `GET /api/v1/bot/` | `GET /api/v1/bots` | List all |
| `GET /bot/{id}/audio/` | `GET /bots/{id}/get_audio` | |
| `GET /bot/{id}/video/` | `GET /bots/{id}/get_video` | |
| `GET /bot/{id}/transcript/` | `GET /transcript/{transcript_id}/get_transcript` | Uses transcript_id |
| `GET /bot/{id}/participants/` | `GET /bots/{id}/get_participants` | |
| `GET /bot/{id}/chat_messages/` | `GET /bots/{id}/get_chats` | |
| `POST /bot/{id}/send_chat_message/` | `bot_message` field on create_bot | Manual review |
| `GET /bot/{id}/screenshots/` | `GET /bots/{id}/get_screenshots` | |
| `GET /bot/{id}/speaker_timeline/` | `GET /bots/{id}/get_speaker_timeline` | |
| `POST /api/v1/webhook/` | `callback_url` on create_bot | Per-bot webhooks |

## Field Mapping

| Recall.ai | MeetStream.ai |
|-----------|---------------|
| `meeting_url` | `meeting_link` |
| `recording_mode` (string) | `video_required` (boolean) |
| `metadata` | `custom_attributes` |
| `transcription_options` | `recording_config.transcript` |
| `noone_joined_timeout` | `voice_inactivity_timeout` |
| `destination_url` | `webhook_url` |
| `output_media.websocket.url` | `socket_connection_url.websocket_url` |

## Webhook Event Mapping

**IMPORTANT:** MeetStream uses a **two-layer event model**, not a flat event list like Recall. You must check `event` first, then `bot_status` for detail — especially for `bot.stopped` where `bot_status` tells you *why* the bot stopped (normal exit, denied, lobby timeout, or error).

The webhook payload format is:
```json
{ "event": "bot.inmeeting", "bot_id": "...", "bot_status": "InMeeting", "message": "...", "status_code": 200, "timestamp": "...", "custom_attributes": {} }
```

### Bot Lifecycle Events

| Recall.ai Event | MeetStream `event` | `bot_status` | Description |
|----------------|-------------------|-------------|-------------|
| `bot.joining_call` | `bot.joining` | `Joining` | Bot connecting to meeting |
| `bot.in_waiting_room` | `bot.joining` | `Joining` | Bot in waiting room |
| `bot.in_call_not_recording` | `bot.inmeeting` | `InMeeting` | Joined, not yet recording |
| `bot.recording_permission_allowed` | `bot.inmeeting` | `InMeeting` | Recording allowed |
| `bot.recording_permission_denied` | `bot.stopped` | `Denied` | Recording denied by host |
| `bot.in_call_recording` | `bot.inmeeting` | `InMeeting` | In-call, recording |
| `bot.call_ended` | `bot.stopped` | `Stopped` | Meeting ended / bot left |
| `bot.call_ended` (lobby_timeout) | `bot.stopped` | `NotAllowed` | Bot could not join (lobby timeout) |
| `bot.done` | `bot.stopped` | `Stopped` | Bot lifecycle ended |
| `bot.fatal` | `bot.stopped` | `Error` | Unexpected error |

### Post-Call Processing Events (MeetStream)

| Recall.ai | MeetStream `event` | Description |
|-----------|-------------------|-------------|
| `bot.done` (then fetch) | `audio.processed` | Audio ready to fetch |
| `bot.done` (then fetch) | `transcription.processed` | Transcript ready to fetch |
| `bot.done` (then fetch) | `video.processed` | Video ready to fetch |
| (no equivalent) | `data_deletion` | Bot data deleted |

### Real-Time Events

| Recall.ai Event | MeetStream | Description |
|----------------|-----------|-------------|
| `transcript.data` | `live_transcription_required` webhook | Transcript utterance |
| `transcript.partial_data` | `live_transcription_required` webhook | Partial transcript |
| `transcript.done` | `transcription.processed` | Full transcript ready |
| `participant_events.join` | Realtime endpoint | Participant joined |
| `participant_events.leave` | Realtime endpoint | Participant left |

### bot_status Reference

| `bot_status` | `event` | Meaning |
|-------------|---------|---------|
| `Joining` | `bot.joining` | Bot is connecting |
| `InMeeting` | `bot.inmeeting` | Bot is in the meeting and recording |
| `Stopped` | `bot.stopped` | Bot exited normally (meeting ended, removed via API, everyone left, voice timeout, etc.) |
| `NotAllowed` | `bot.stopped` | Bot could not join (commonly waiting room / lobby timeout) |
| `Denied` | `bot.stopped` | Host explicitly denied join or recording permission |
| `Error` | `bot.stopped` | Unexpected error during lifecycle |

## Breaking Changes to Watch

These are flagged during migration and require manual review:

1. **Transcript retrieval** — MeetStream uses `transcript_id` (returned in create_bot response), not `bot_id`
2. **Recording mode** — Recall uses strings (`speaker_view`, `gallery_view`, `audio_only`), MeetStream uses a boolean `video_required`
3. **Chat messages** — Recall has a separate `POST /send_chat_message/` endpoint, MeetStream uses the `bot_message` field on `create_bot`
4. **Webhooks** — Recall registers webhooks globally via `POST /webhook/`, MeetStream uses `callback_url` per bot on creation
5. **Webhook events** — Recall uses granular string events (`bot.joining_call`, `bot.in_call_recording`), MeetStream uses 3 lifecycle events (`bot.joining`, `bot.inmeeting`, `bot.stopped`) with `bot_status` for detail
6. **Post-call processing** — Recall signals readiness via `bot.done`, MeetStream sends separate `audio.processed`, `transcription.processed`, and `video.processed` webhooks
7. **Live transcription** — Recall uses `real_time_transcription.destination_url`, MeetStream uses `live_transcription_required.webhook_url` on create_bot

## Post-Migration Checklist

After running the migration:

1. Set `MEETSTREAM_API_KEY` in your `.env` file (get one at [app.meetstream.ai](https://app.meetstream.ai))
2. Review all warnings from the migration output
3. Update webhook handlers to use `event` + `bot_status` instead of Recall event names
4. Store `transcript_id` from create_bot responses for transcript retrieval
5. Add handlers for `audio.processed`, `transcription.processed`, `video.processed` webhooks
6. Verify `recording_mode` → `video_required` boolean logic
7. Run `npx @meetstream/migrate test --api-key YOUR_KEY` to verify

## CLI Options

```
npx @meetstream/migrate [command] [path] [options]

Commands:
  scan [path]        Scan only — show what would change
  migrate [path]     Scan & migrate a project (default)
  test               Run API tests against MeetStream.ai

Options:
  --yes, -y          Skip confirmation prompt
  --dry-run          Show changes without writing files
  --api-key KEY      MeetStream API key (for test command)
  --verbose, -v      Detailed output
  --help, -h         Show help
```

## Requirements

- Node.js >= 16.0.0

## License

MIT

---

**Need help migrating?** Reach out at [meetstream.ai](https://meetstream.ai) — we'll walk you through it.