Skip to content

feat: local HTTP control API for external recording start/stop#499

Open
ryanhugh wants to merge 1 commit into
Zackriya-Solutions:mainfrom
ryanhugh:enhance/local-control-api
Open

feat: local HTTP control API for external recording start/stop#499
ryanhugh wants to merge 1 commit into
Zackriya-Solutions:mainfrom
ryanhugh:enhance/local-control-api

Conversation

@ryanhugh
Copy link
Copy Markdown

@ryanhugh ryanhugh commented Jun 4, 2026

What

Adds a localhost-only HTTP control API (127.0.0.1:5172) inside the Tauri app so other local processes (scripts, Raycast, window managers, automations) can control recordings:

Endpoint Description
GET /status {"recording": bool}
POST /start Body {"title"?: string, "metadata"?: string} — starts a recording. Optional custom meeting title; optional free-form metadata block that is saved as the first transcript segment (top of the meeting, included in summarization). Returns 200 once recording actually starts, 409 if already recording, 504 if the frontend refuses (e.g. no transcription model).
POST /stop Stops the recording and runs the normal save flow (transcription flush → SQLite save → navigation). 409 if not recording.

Example:

curl -X POST http://127.0.0.1:5172/start \
  -H 'content-type: application/json' \
  -d '{"title": "Standup", "metadata": "Attendees: Ryan, Alice\nTicket: ENG-123"}'

curl -X POST http://127.0.0.1:5172/stop

How

The API deliberately reuses the existing tray mechanisms rather than inventing a new path:

  • Start stages parameters in sessionStorage and triggers the same auto-start flow the tray uses (useRecordingStart), so model checks, device selection, meeting state, and save flow behave exactly like a button click.
  • Stop uses a new shared recording_commands::stop_recording_and_notify() helper — both tray handlers were duplicating ~30 lines of this logic and now call the same helper.
  • Metadata is injected as a synthetic transcript segment (sequence_id: -1, sorts before all real segments) both live and at save time, so it survives mid-recording page reloads and state resyncs.
  • Server is axum on the existing tokio runtime, bound to 127.0.0.1 only (same trust level as the tray). Bind failure logs and degrades gracefully.

Pre-existing bugs fixed along the way

  1. Selected devices and meeting name were silently dropped on every recording start: recordingService.startRecordingWithDevices passed snake_case arg keys (mic_device_name, meeting_name) to a Tauri v2 command expecting camelCase, so Rust always received None (log: Mic: None, System: None, Meeting: None) and every meeting got the default Meeting YYYY-MM-DD… title regardless of the UI-generated one.
  2. Clean builds of main fail with the committed Cargo.lock (tauri 2.11.2): generate_handler! requires the generated __tauri_command_name_* macros, but summary/mod.rs and summary_engine/mod.rs re-exported only the old __cmd__* ones → 15 × E0433. Added the missing re-exports.

Testing

Verified end-to-end on macOS (Apple Silicon, dev build):

  • GET /status{"recording":false} / {"recording":true}
  • POST /start with title + metadata → app navigates home, recording starts, 200 {"status":"recording","title":"API Test Round 2"}; Rust log confirms Meeting: Some("API Test Round 2")
  • POST /start while recording → 409 ✅ ; POST /stop while idle → 409
  • Spoke during recording, then POST /stop → normal save flow; SQLite shows the meeting titled API Test Round 2 with the metadata block as the first segment (audio_start_time = 0) followed by the real transcribed speech ✅
  • cargo check and tsc --noEmit clean ✅

🤖 Generated with Claude Code

Adds a localhost-only (127.0.0.1:5172) HTTP API inside the Tauri app so
other local processes (scripts, Raycast, automations) can control
recordings:

- GET  /status -> {"recording": bool}
- POST /start  -> {"title"?, "metadata"?}; optional meeting title and a
  free-form metadata block that is saved as the first transcript segment
  (so it appears at the top of the meeting and feeds summarization)
- POST /stop   -> stops recording and runs the normal save flow

Implementation reuses the existing tray mechanisms: start goes through
the frontend auto-start flow (sessionStorage flag + navigation), stop
goes through a new shared stop_recording_and_notify() helper that the
two tray handlers now also use instead of duplicating the logic.

Also fixes two pre-existing bugs found along the way:

- recordingService.startRecordingWithDevices passed snake_case arg keys
  to a camelCase-expecting Tauri v2 command, so the selected devices and
  meeting name were silently dropped (every meeting got the default
  "Meeting YYYY-MM-DD..." title regardless of what the UI generated)
- summary/mod.rs and summary_engine/mod.rs re-exported only the
  __cmd__* macros, missing the __tauri_command_name_* macros that the
  locked tauri 2.11 generate_handler! requires, breaking clean builds

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant