diff --git a/.env.example b/.env.example
index dd39fbc..1e93e00 100644
--- a/.env.example
+++ b/.env.example
@@ -12,6 +12,10 @@ TURN_CREDENTIAL=
# Optional: open self-registration of the first/any team (1 to enable).
# ALLOW_REGISTRATION=1
+# Optional: BizGaze as the identity provider. When set, /api/login validates
+# credentials against this endpoint (after a local check) and provisions the user.
+# BIZGAZE_LOGIN_URL=https://c02.bizgaze.app/Account/ValidateAndLogin
+
# Optional: shared secret for BizGaze SSO + signed webhook delivery.
# SSO_SECRET=
diff --git a/.gitignore b/.gitignore
index 0625426..d0131be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,6 +29,10 @@ dist/
build/
out/
+# Runtime media (created at startup by config.js)
+server/recordings/
+server/transcripts/
+
# OS files
.DS_Store
Thumbs.db
diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md
new file mode 100644
index 0000000..0ebd370
--- /dev/null
+++ b/ARCHITECTURE.md
@@ -0,0 +1,163 @@
+# BizGaze Connect — Architecture & Roadmap
+
+This document records the **current** architecture, the **target** architecture, and a
+**phased migration plan** so that the three strategic goals can be added *additively*
+rather than as rewrites.
+
+Strategic goals (see also `CLAUDE.md`):
+1. **Native Android/iOS apps**
+2. **Integration with any third-party application**
+3. **Org-based licensing model** (Zoom-like: organizations buy seats/plans)
+
+---
+
+## 1. Current architecture (as of 2026-06)
+
+```
+Single Node process (server/server.js, ~640 lines)
+├── HTTP JSON API (/api/*, cookie-session auth)
+├── WebSocket signaling (/ws — SDP/ICE relay, consent, share codes)
+├── Static file serving (public/*.html, single-file pages, no build)
+└── In-process state liveSessions / onlineAgents / pendingShares (Maps)
+
+Data: node:sqlite single file (server/data.db)
+ teams, users, sessions_auth, machines, audit_log, sessions_log
+Media: WebRTC P2P (1:1). STUN + managed TURN. Media never traverses the server.
+Auth: scrypt passwords, opaque session token in an HttpOnly `sid` cookie. TOTP code exists but
+ login currently marks sessions MFA-passed directly.
+Integrations: outbound webhook (single env URL, `session.ended`, HMAC-signed);
+ inbound SSO (`/sso`, custom HMAC token).
+Recordings/transcripts: written to local disk (server/recordings, server/transcripts).
+```
+
+### What is already future-proof (keep)
+- **WebRTC + `/ws` signaling** — standards-based; reused as-is by native apps and an SFU.
+- **P2P media** — no server media path for 1:1; cheap and private.
+- **HMAC-signed webhooks** and **audit log** — right primitives, just need to scale out.
+- **Team-scoped queries** — the seed of multi-tenancy is present.
+
+### Structural constraints that block the roadmap
+| # | Constraint | Blocks |
+|---|-----------|--------|
+| C1 | Auth is **cookie-only** (`parseCookies(req).sid`); no `Authorization: Bearer`, no API keys | Mobile, Integrations |
+| C2 | **No API versioning** (`/api/...`) | Mobile (shipped clients pin a contract) |
+| C3 | **`team` is a thin tenant** `(id,name,created_at)`; app assumes one team | Licensing |
+| C4 | **Session state in process memory** (Maps) | Horizontal scale, Meetings |
+| C5 | **SQLite single-writer**, queries inline at ~100 call-sites | Scale, multi-tenant isolation |
+| C6 | **P2P mesh only** — no SFU | Multi-party meetings (Zoom-like) |
+| C7 | **Recordings on local disk** | Multi-instance, per-org storage quotas |
+| C8 | **Monolithic `server.js`** mixes HTTP/WS/static/logic/DB | All (maintainability) |
+
+---
+
+## 2. Target architecture (principles)
+
+1. **Organization is the top-level tenant.** Every row and every request resolves to an
+ `org_id`. Billing, seats, plan, and feature flags hang off the Organization.
+2. **Data access goes through a repository layer**, never raw SQL in route handlers.
+ This is what makes the SQLite→Postgres migration and strict tenant-scoping feasible.
+3. **The API is versioned and token-addressable.** `/api/v1`; auth accepted via cookie
+ (web) *or* `Authorization: Bearer` (mobile) *or* scoped API key (integrations).
+4. **Shared runtime state lives outside the process** (Redis) so the app can run N instances.
+5. **Multi-party media uses an SFU** (LiveKit/mediasoup); 1:1 may stay P2P.
+6. **Entitlements are enforced centrally** — one middleware checks plan limits before
+ privileged actions (add seat, start meeting, record, call the API).
+
+```
+ ┌──────────── clients ────────────┐
+ │ web (HTML) mobile (native) 3rd-party (API key) │
+ └───────┬───────────┬───────────────┬──────────────┘
+ │ cookie │ Bearer │ API key
+ ┌───────▼───────────▼───────────────▼──────────────┐
+ │ API v1 (routes → services → repository) │
+ │ authN (cookie/Bearer/key) · authZ (RBAC) │
+ │ entitlements middleware (plan/seat/feature) │
+ └───────┬───────────────────────┬──────────────────┘
+ │ │
+ ┌─────────▼────────┐ ┌─────────▼─────────┐
+ │ Repository layer │ │ Signaling (ws) │──── Redis (shared state,
+ │ (SQLite→Postgres)│ │ + SFU for meetings│ pub/sub across instances)
+ └─────────┬────────┘ └───────────────────┘
+ │
+ Postgres · Object storage (recordings) · usage-metering
+```
+
+---
+
+## 3. Goal-by-goal requirements
+
+### Goal 1 — Native Android/iOS
+- **Bearer-token auth** (C1) + refresh tokens; device registration.
+- **`/api/v1`** (C2) — stable, documented contract.
+- **Push notifications** (APNs/FCM) for incoming sessions/calls (mobile can't hold a background WS).
+- Reuse WebRTC/`/ws` via `react-native-webrtc`/native SDKs; native screen capture
+ (ReplayKit / MediaProjection) for the phone-can't-share gap.
+
+### Goal 2 — Third-party integration
+- **Scoped API keys / OAuth2 client-credentials** per org (C1).
+- **Webhook subscriptions** per org: multiple endpoints, event types, signed payloads, retries.
+- **OIDC/JWT SSO** to replace the custom HMAC `/sso`.
+- Optional: embeddable JS widget / SDK.
+
+### Goal 3 — Org licensing (Zoom-like)
+- **Organization** entity (C3): `plan`, `seats`, `status`, `trial_ends_at`, feature flags.
+- **Entitlements + metering**: tables for plan limits and usage (minutes, sessions, storage);
+ central enforcement middleware.
+- **SFU** for multi-party meetings (C6); per-org concurrent-meeting / minute caps.
+- **Redis shared state** (C4) for multi-instance; **Postgres** (C5); **object storage** (C7).
+- Billing provider integration (e.g. Stripe) driving subscription state.
+
+---
+
+## 4. Phased plan
+
+> **Priority (set by the user 2026-06-11): mobile + integration first; licensing last.**
+> Principle unchanged: do the shared groundwork first, so later work is additive. Because
+> licensing is last, the full **Organization** entity moves to Phase 3 with it — Phase 1 keeps
+> only a *tenant-id abstraction* (mapping to today's `team_id`) so Phase-2 auth/keys don't need
+> reworking when the tenant is later elevated to a full Organization.
+
+### Phase 1 — Foundations (structural, no behavior change) ✅ DONE (2026-06-11)
+- [x] Extracted a **data-access layer** (`repos.js`) — all SQL moved out of `server.js`.
+- [x] **Modularized** `server.js` → `config / lib / session / presence / routes / static / signaling`
+ (plus `repos` data layer and `bizgaze` service). `server.js` is now a thin entry point.
+- [x] Standardized a **tenant id** in the data layer (repo params named `tenantId`, == `team_id` today);
+ every query is tenant-scoped. *No Organization entity / plan-seats yet — that's Phase 3.*
+- Verified behavior-preserving by `test/e2e.js` (21/21) before and after.
+
+### Phase 2 — API + access ← **PRIORITY** (mobile + desktop + integrations)
+Target clients: web (cookie), native **Android/iOS**, a native **Windows desktop app where the
+viewer CONTROLS the remote screen** (reuses the WebRTC `inputChannel` + OS input injection like
+`agent/`), and third-party systems (API keys). All authenticate through this one access layer.
+- [x] **`/api/v1`** — every `/api/*` route aliased under `/api/v1/*` (routes.js); web keeps unversioned paths.
+- [x] **`Authorization: Bearer `** accepted in `currentUser()` across HTTP + WS, alongside the
+ cookie (session.js `tokenFromReq`); `/api/login` now also returns the `token` for native clients.
+ WS upgrades carry the token in the Authorization header (native) or `?access_token=` (browser fallback).
+- [ ] **Refresh tokens** (short access token + long refresh) so native apps stay signed in safely.
+- [ ] **API keys** table + middleware (scoped per *tenant*, hashed at rest).
+- [ ] **Push-notification hooks** (APNs/FCM) for incoming sessions/calls on mobile.
+- [ ] **OIDC/JWT** SSO; per-tenant **webhook subscriptions** with retries.
+
+### Phase 3 — Licensing + scale (last, per priority)
+- [ ] Elevate **tenant → `organization`** (additive migration: add `organizations`, keep `team_id`
+ as alias/FK); add `plan`, `seats`, `status`, `features` columns.
+- [ ] **Entitlements** module + central enforcement; **usage metering** (minutes/sessions/storage).
+- [ ] **SFU** (LiveKit/mediasoup) for multi-party meetings; keep 1:1 P2P.
+- [ ] **Redis** for `liveSessions/onlineAgents/pendingShares` + cross-instance pub/sub.
+- [ ] **Postgres** migration (enabled by the Phase-1 data-access layer); **object storage** (S3) for recordings.
+- [ ] Billing provider + subscription lifecycle webhooks.
+
+### Explicitly NOT yet
+- Don't add an SFU or Postgres before the data-access layer exists — you'd rework them.
+- Don't build the full Organization/plan model before Phase 2 ships — but *do* keep the tenant-id
+ abstraction consistent from Phase 1 so the elevation is additive.
+- Don't shard or add microservices; a well-modularized monolith + Redis + Postgres scales far enough.
+
+---
+
+## 5. Key decisions to confirm (when we reach them)
+- **Auth tokens:** opaque (DB-backed, easy revoke) vs JWT (stateless, harder revoke). *Lean: opaque
+ access + refresh, since `sessions_auth` already works that way.*
+- **SFU:** LiveKit (batteries-included, good mobile SDKs) vs mediasoup (lower-level, more control).
+- **DB:** Postgres (recommended) — keep the repository layer DB-agnostic until the cutover.
+- **Billing:** Stripe vs BizGaze's own billing (depends on how Connect sits inside the BizGaze suite).
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..3f18415
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1,134 @@
+# BizGaze Connect — project brief
+
+Place this file at the repo root (`remote-access-app/CLAUDE.md`). Claude Code reads
+it automatically each session.
+
+## What this is
+**BizGaze Connect** — a no-install, browser-based remote support / screen-sharing
+tool for the BizGaze ecosystem. A customer opens a page, gets a 6-digit code; a
+signed-in BizGaze agent enters the code, the customer taps Allow, and the agent
+sees the customer's screen with two-way voice + chat. Live at **remote.bizgaze.com**.
+Roadmap: grow into a communication platform (meetings + persistent chat) for
+registered BizGaze users.
+
+## Tech stack (intentionally minimal — keep it this way)
+- **Node.js >= 22.5**, single npm dependency: `ws` (WebSocket).
+- **Built-in `node:sqlite`** (no native modules). DB file: `server/data.db`.
+- **WebRTC** peer-to-peer for media (screen video + voice + data channels).
+- **No build step, no framework.** Each page is a single self-contained HTML file
+ with inline `
+
+← Home
BizGaze Support
@@ -60,7 +71,10 @@ const IS_MOBILE=/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|M
let __icePromise=Promise.resolve();try{__icePromise=fetch('/api/ice').then(r=>r.ok?r.json():null).then(c=>{if(c&&c.iceServers&&IS_MOBILE)ICE=c;}).catch(()=>{});}catch(_){}
async function ensureIce(){try{await __icePromise;}catch(_){}return ICE;}
function pEsc(s){return String(s==null?'':s).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c]));}
-function profileHTML(name){return '
';}
+// When embedded in the home shell, tell the parent when a session is live so the
+// rail can show a "return here" indicator.
+function bzcSession(active){try{if(window.parent&&window.parent!==window)window.parent.postMessage({type:'bzc-session',flow:'connect',active:!!active},location.origin);}catch(_){}}
+function profileHTML(name){return '
Soon you'll be able to host multi-party video meetings with your BizGaze team and customers — right here, no install needed. We're putting on the finishing touches.
+
+
In the meantime, use Share Screen or Connect Screen to start a session.
+
+
+
+
+
+
+
+
+
+
Share your screen
+
Let a teammate or customer see your screen instantly. You'll get a 6-digit code to share — they enter it to connect. No download, works right in the browser.