Metadata-Version: 2.4
Name: meetstream-migrate
Version: 2.3.0
Summary: One-command migration from Recall.ai to MeetStream.ai — scans your codebase, maps APIs, transforms code.
Home-page: https://github.com/meetstream/recall-meetstream-migration-kit
Author: MeetStream
Author-email: sid@meetstream.ai
License: MIT
Keywords: meetstream,recall,migration,meeting-bot,api
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: posthog
Requires-Dist: rich>=13.0.0
Requires-Dist: requests>=2.28.0
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: keywords
Dynamic: license
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# 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 — safely, with warnings for anything that needs a human look.

> **v2.1.2 — near feature parity with Recall.ai (verified live against production, July 2026).**
> MeetStream matches Recall across recording, per-participant streams, real-time audio/video/
> transcript, chat + image output, Google **and** Outlook calendar, scheduling, signed-in bots,
> and retention — plus native AI summaries and MIA voice agents that Recall doesn't have. The
> only gaps: **pause/resume recording** and **live video output** into the call (both shipping
> soon). See [Feature parity & gaps](#feature-parity--gaps) below.

---

## Quick Start

```bash
pip install meetstream-migrate
meetstream-migrate scan ./your-recall-project
```

## Commands

```bash
# Scan (read-only) — finds every Recall.ai reference, writes NOTHING
meetstream-migrate scan ./your-project

# Dry run — shows exactly what would change, still writes nothing
meetstream-migrate migrate ./your-project --dry-run

# Migrate — applies all transformations (prompts before writing)
meetstream-migrate migrate ./your-project --yes

# Verify your MeetStream credentials + API reachability
meetstream-migrate test --api-key YOUR_MEETSTREAM_API_KEY
```

### CLI reference

```
meetstream-migrate <command> [path] [options]

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

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

## What it does

**1. Scan** — recursively finds:
- Recall.ai base URLs (`us-east-1.recall.ai`, `eu-west-1.recall.ai`, `api.recall.ai`)
- API endpoint calls (`/api/v1/bot/`, `/api/v1/bot/{id}/audio/`, etc.)
- Recall-specific field names (`meeting_url`, `recording_mode`, `transcription_options`, `metadata`, …)
- Webhook handlers (`bot.status_change`, `joining_call`, `in_call_recording`, `call_ended`, `done`, `fatal`, …)
- SDK imports (`require('recall-ai')`, `import recall`)
- Environment variables (`RECALL_API_KEY`, `RECALL_API_URL`, `RECALL_WEBHOOK_SECRET`)

Supported file types: `.js .ts .jsx .tsx .mjs .cjs .py .go .rb .java .kt .rs .php .cs .swift .env .yaml .yml .json .toml .ini .cfg`

**2. Display** — endpoint mapping table, field mapping table, webhook event mapping, and a compatibility check (auto-migratable vs. needs manual review).

**3. Transform** — base URL swap, endpoint path rewrites, field renames, webhook event renames, env var renames, import/require updates. Every change carries an inline warning when it needs a second look.

**4. Report** — what changed, what needs manual review, and a post-migration checklist.

---

## Endpoint Mapping

| Recall.ai | MeetStream.ai | Notes |
|-----------|---------------|-------|
| `POST /api/v1/bot/` | `POST /api/v1/bots/create_bot` | Field names differ (see below) |
| `GET /api/v1/bot/{id}/` | `GET /api/v1/bots/{id}/detail` | Also `GET /api/v1/bots/{id}/status` |
| `POST /api/v1/bot/{id}/leave_call/` | `GET /api/v1/bots/{id}/remove_bot` | ⚠️ Method changes POST → GET |
| `DELETE /api/v1/bot/{id}/` | `DELETE /api/v1/bots/{id}/delete` | |
| `GET /api/v1/bot/` | `GET /api/v1/bots` | List all bots |
| `GET /bot/{id}/audio/` | `GET /bots/{id}/get_audio` | Presigned S3 URL, valid 1h |
| `GET /bot/{id}/video/` | `GET /bots/{id}/get_video` | Presigned S3 URL, valid 10min |
| `GET /bot/{id}/transcript/` | `GET /transcript/{transcript_id}/get_transcript` | ⚠️ Uses `transcript_id`, not `bot_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/` | `POST /bots/{id}/send_message` | REST body `{ "message": "..." }` — drop Recall's `to`/`pin` fields |
| `POST /bot/{id}/output_video/` | `POST /bots/{id}/send_image` | Public `img_url` (not base64) |
| `POST /bot/{id}/output_audio/` | WebSocket `sendaudio` command | ⚠️ Manual — no REST equivalent, use `socket_connection_url` bridge |
| `POST /bot/{id}/output_media/` | WebSocket bridge (partial) | ⚠️ Manual — covers audio + static image today |
| `POST /bot/{id}/pause_recording/` | — | ⛔ Not yet supported (shipping soon) |
| `POST /bot/{id}/resume_recording/` | — | ⛔ Not yet supported (shipping soon) |
| `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` field on `create_bot` | Per-bot, not account-level |
| `GET /bot/{id}/calendar/…` | `POST /calendar/create_calendar` (underscore) | Body: `google_client_id`/`google_client_secret`/`google_refresh_token` |

## Field Mapping

| Recall.ai | MeetStream.ai | Notes |
|-----------|---------------|-------|
| `meeting_url` | `meeting_link` | |
| `recording_mode` (string) | `video_required` (boolean) | `"speaker_view"`/`"gallery_view"` → `true`, `"audio_only"` → `false` |
| `metadata` | `custom_attributes` | Values must be strings |
| `transcription_options` | `recording_config.transcript.provider.{provider}` | Object structure differs — see provider config |
| `noone_joined_timeout` | `voice_inactivity_timeout` | |
| `destination_url` | `webhook_url` (nested in `live_transcription_required`) | |
| `assembly_ai` | `assemblyai` | Also: `assemblyai` uses `speech_models[]` + `language_code`, not `model`/`language` |
| `output_media.websocket.url` | `socket_connection_url.websocket_url` | |

## Webhook Events — live-verified against production

MeetStream's envelope key is **`event`** — the **same key Recall uses**. No envelope rename needed. Alongside it: `bot_id`, `message`, `status_code` (200 success / 500 failure), `custom_attributes`, and on lifecycle events, `bot_status`.

```json
{ "event": "bot.inmeeting", "bot_id": "...", "bot_status": "InMeeting",
  "message": "...", "status_code": 200, "custom_attributes": {} }
```

**Two-layer model:** `bot.stopped` fires **once**; `bot_status` tells you *why* — `Stopped` (normal) / `NotAllowed` (lobby timeout) / `Denied` (host denied) / `Error` (crash). Recall's separate end-reason events collapse into this single event + status.

| Recall.ai Event | MeetStream `event` | `bot_status` | status_code |
|----------------|--------------------|--------------|-------------|
| `bot.joining_call` | `bot.joining` (may fire up to 3×) | `Joining` | 200 |
| `bot.in_waiting_room` | `bot.in_waiting_room` | `InWaitingRoom` | 200 |
| `bot.in_call_not_recording` | `bot.inmeeting` | `InMeeting` | 200 |
| `bot.in_call_recording` | `bot.recording` | `Recording` | 200 |
| _(none — new)_ | `bot.leaving` | `Leaving` | 200 |
| _(none — new)_ | `bot.error` (non-terminal streaming-provider error; bot keeps running) | `InMeeting` | — (omitted) |
| `bot.call_ended` | `bot.stopped` | `Stopped` | 200 |
| `bot.call_ended` (lobby timeout) | `bot.stopped` | `NotAllowed` | 500 |
| `bot.call_ended` (host denied) | `bot.stopped` | `Denied` | 500 |
| `bot.fatal` | `bot.stopped` | `Error` | 500 |
| `recording.done` | `manifest.completed` → `audio.processed` → `video.processed` (if `video_required`) | — | 200 |
| `transcript.done` | `transcription.processed` | — | 200 |
| `transcript.failed` | `transcription.failed` | — | **500** |
| _(post-call)_ | `bot.done` — post-call providers only | `Done` | 200 / 500 |
| _(retention)_ | `data_deletion` | — | 200 |

**Streaming-only providers** (`deepgram_streaming`, `assemblyai_streaming`, `meeting_captions`) do **not** fire `transcription.processed`/`transcription.failed`/`bot.done` — their terminal event is `audio.processed`. Don't wait on `bot.done` for a streaming bot, or your handler hangs forever.

**`transcript_id` is never in the webhook payload.** Resolve it via `GET /bots/{id}/detail` (`bot_details.transcript_id`), the `create_bot` response, or `GET /bots/{id}/transcriptions`.

**Full `bot_status` enum:** `Joining`, `InWaitingRoom`, `InMeeting`, `Recording`, `Leaving`, `Stopped`, `NotAllowed`, `Denied`, `Error`, `Done`.

### Real-time events

| Recall.ai | MeetStream | Notes |
|-----------|------------|-------|
| `transcript.data` / `transcript.partial_data` | delivered to `live_transcription_required.webhook_url` | Chunk shape: `{ speakerName, timestamp, transcript, words[] }` |
| `participant_events.join` / `.leave` | same event names, requires `recording_config.realtime_endpoints` | Different payload shape — nested under `data.bot.{id,metadata}`, no top-level `bot_id` |

---

## Breaking changes to review manually

1. **Transcript retrieval** uses `transcript_id` (from the `create_bot` response, `/detail`, or `/transcriptions`), not `bot_id`.
2. **`recording_mode`** (Recall string) → **`video_required`** (MeetStream boolean).
3. **Chat messages**: Recall's separate `POST /send_chat_message/` → MeetStream's `POST /bots/{id}/send_message`.
4. **Webhook registration**: Recall registers globally via `POST /webhook/`; MeetStream uses `callback_url` per bot at creation.
5. **Webhook event values** rename (see table above) — but the **envelope key stays `event`**, so most handler code needs a value rename, not a restructure.
6. **Post-call processing** arrives as discrete events (`manifest.completed`, `audio.processed`, `transcription.processed`/`transcription.failed`, `video.processed`, `bot.done`) instead of one `bot.done`-then-fetch signal.
7. **Live transcription**: `real_time_transcription.destination_url` → `live_transcription_required.webhook_url` on `create_bot`.
8. **Pause/resume recording** has no MeetStream equivalent yet — remove these calls, or segment via `remove_bot` + re-create.
9. **Output audio/media** into the call is REST on Recall, WebSocket-bridge on MeetStream — needs a real refactor, not a rename (flagged, not auto-migrated).

## Post-Migration Checklist

1. Set `MEETSTREAM_API_KEY` (get one at [app.meetstream.ai/api-keys](https://app.meetstream.ai/api-keys))
2. Review every ⚠️ warning from the migration output
3. Update webhook handlers: keep branching on `event`, rename the values, and check `bot_status` for `bot.stopped`'s reason
4. Store `transcript_id` for transcript retrieval — from `create_bot`, `/detail`, or `/transcriptions`
5. Add handlers for `manifest.completed`, `audio.processed`, `transcription.processed`/`failed`, `video.processed`
6. Verify the `recording_mode` → `video_required` boolean logic
7. Remove any `pause_recording`/`resume_recording` calls (not supported yet)
8. Run `meetstream-migrate test --api-key YOUR_KEY` to confirm connectivity

---

## Feature parity & gaps

| Recall capability | MeetStream | Migrator behaviour |
|---|---|---|
| Create/list/get/remove/delete bot | ✅ | auto-mapped |
| Recording (audio/video) + retention | ✅ | auto-mapped |
| Per-participant streams | ✅ | `get_audio_streams` / `get_recording_streams` |
| Real-time audio/video/transcript | ✅ | `live_audio_required` / `live_video_required` / `live_transcription_required` |
| Send chat message | ✅ | `send_chat_message` → REST `send_message` |
| Output image/camera frame | ✅ | `output_video` → `send_image` (public URL) |
| Calendar (Google + Outlook) + scheduling | ✅ | `calendar/events/{id}/bot` → `calendar/schedule/{id}` |
| Signed-in / OAuth bots | ✅ | Google login domains/logins; Zoom OBF |
| AI summaries | ✅ (MeetStream-only) | `GET /bots/{id}/summary` |
| Conversational voice agents (MIA) | ✅ (MeetStream-only) | `agent_config_id` on `create_bot` |
| Output audio / interactive media | ⚠️ partial | WebSocket bridge (manual, flagged) |
| **Pause/resume recording** | ⛔ not yet | flagged — shipping soon |
| **Live video output into the call** | ⛔ not yet | `send_image` covers static frames only |

Run `scan` first to see exactly which of these appear in your codebase before migrating.

---

## Requirements

Python ≥ 3.8

## Development

```bash
pip install -e .
python -m pytest test/    # or: python -m meetstream_migrate.cli test --api-key YOUR_KEY
```

## License

MIT © MeetStream.ai — [meetstream.ai](https://meetstream.ai) · [docs.meetstream.ai](https://docs.meetstream.ai)
