mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 21:30:39 -07:00
Mobile companion app + paired-device backend
New iOS-first companion (Expo SDK 54 + NativeWind v4) with three tabs: Captures (the hero — floating gold mic, live mic-meter waveform, expand-row playback), Generate (profile picker + speak + autoplay + recent), and Voices (searchable profile list). Pairing (V0): backend mints a one-time token, mobile scans/pastes the voicebox:// URL, server returns a long-lived bearer it stores only as a SHA-256 hash. Bearer-or-loopback auth applied to every user-data router so binding 0.0.0.0 doesn't leak existing endpoints. Loopback callers (the desktop app) keep their friction-free access. Desktop Settings → Mobile pane: live host picker (LAN / Tailscale auto- detected via the App-bundle binary path on macOS), QR rendering, 5-minute expiry countdown, copyable URL fallback, paired-device list with revoke. Auto-closes when a new device pairs. just dev now binds the backend to 0.0.0.0 so paired phones can reach it — and just setup-python pins mlx-audio==0.4.1 + mlx-lm so fresh Apple Silicon worktrees get a working STT path on first install.
This commit is contained in:
@@ -279,3 +279,37 @@ class Capture(Base):
|
||||
llm_model = Column(String, nullable=True)
|
||||
refinement_flags = Column(Text, nullable=True) # JSON blob
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class PairedDevice(Base):
|
||||
"""A mobile device paired with this Voicebox install (V0 pair flow).
|
||||
|
||||
Stores only the SHA-256 of the bearer token; the bearer plaintext is
|
||||
returned to the device once at pairing time and never persisted
|
||||
server-side. If the device loses its bearer the user must re-pair.
|
||||
"""
|
||||
|
||||
__tablename__ = "paired_devices"
|
||||
|
||||
id = Column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
name = Column(String, nullable=False)
|
||||
bearer_hash = Column(String, nullable=False, unique=True, index=True)
|
||||
revoked = Column(Boolean, default=False, nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
last_seen_at = Column(DateTime, nullable=True)
|
||||
|
||||
|
||||
class PairingToken(Base):
|
||||
"""One-time token used to complete a device pairing.
|
||||
|
||||
Minted by the desktop UI via /pair/init, redeemed by the mobile
|
||||
device via /pair/complete in exchange for a long-lived bearer.
|
||||
Single-use; expires after ~5 minutes.
|
||||
"""
|
||||
|
||||
__tablename__ = "pairing_tokens"
|
||||
|
||||
token = Column(String, primary_key=True)
|
||||
expires_at = Column(DateTime, nullable=False)
|
||||
used_at = Column(DateTime, nullable=True)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
Reference in New Issue
Block a user