Skip to content

feat: add headless file transcription CLI - #200

Merged
jatinkrmalik merged 3 commits into
VocaHQ:mainfrom
Mr-Sunglasses:feat/headless-transcription-cli
Aug 13, 2026
Merged

feat: add headless file transcription CLI#200
jatinkrmalik merged 3 commits into
VocaHQ:mainfrom
Mr-Sunglasses:feat/headless-transcription-cli

Conversation

@Mr-Sunglasses

Copy link
Copy Markdown
Member

Summary

  • dispatch headless commands before SwiftUI constructs VocaMacApp, so CLI invocations never create AppState, run single-instance termination, or initialize GUI/microphone/hotkey services
  • resolve the saved or one-request model through ModelManager, enforce supported/downloaded checks, and transcribe every engine through TranscriptionRouter
  • normalize regular audio files to mono 16 kHz Float32 PCM and expose stable JSON for transcription results, model listings, and categorized failures
  • isolate stdout/stderr from late engine/runtime logging so successful JSON remains exactly one clean object and failures remain concise
  • document the headless contract separately from the existing launch/build helpers

Validation

  • swift build
  • swift test — 293 passed, 1 existing hardware-dependent skip
  • make build — passed with ad-hoc signing; existing ONNX framework-symlink warning remains
  • git diff --check
  • real 4.06-second speech WAV with Parakeet v2: valid JSON, engine: parakeet, GUI PID unchanged
  • real speech WAV with explicit Whisper Small override: valid JSON, engine: whisperkit, saved Parakeet selection unchanged
  • bundled --list-models --json: one valid JSON line, selected model correct, zero stderr bytes
  • missing audio and undownloaded model: nonzero categorized JSON errors, GUI PID unchanged, no orphan process

Notes

One-shot CLI mode intentionally loads the selected model in a separate process for each request. Persistent serving, daemon, HTTP, and IPC modes remain out of scope for this PR.

@github-actions github-actions Bot added app ci enhancement New feature or request docs and removed ci labels Aug 8, 2026
Comment thread Sources/VocaMac/CLI/CLIEntrypoint.swift
Comment thread Tests/VocaMacTests/CLITests.swift
Comment thread Sources/VocaMac/CLI/CLICommand.swift Outdated
Comment thread Sources/VocaMac/CLI/HeadlessTranscriber.swift
Comment thread Sources/VocaMac/CLI/HeadlessTranscriber.swift Outdated

@jatinkrmalik jatinkrmalik left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Mr-Sunglasses There are a few comments by the bot, can you address them?

…l fallback

- Restrict CLI dispatch to recognized flags so GUI-only launch args like
  --restarted (Settings -> Debug -> Restart) route to the GUI instead of
  being rejected by the CLI parser
- Narrow ensureSingleInstance()'s pgrep fallback to skip processes running
  a headless CLI job, so opening/relaunching the GUI no longer kills an
  in-flight --transcribe-file request
- Fall back to the Tiny model when no selection is persisted, matching
  AppState's @AppStorage default, instead of failing on fresh installs
- Document that translate and custom vocabulary are always off headlessly
- Add tests for the --restarted dispatch case and the missing-preference
  fallback
@Mr-Sunglasses

Copy link
Copy Markdown
Member Author

Addressed all review comments from cursor[bot] in 4319eaf:

  • HighinvocationMode now only dispatches to the CLI on a recognized flag (--transcribe-file, --list-models, --help/-h), so --restarted (Settings → Debug → Restart) reaches the GUI again instead of being rejected by the CLI parser.
  • MediumensureSingleInstance()'s pgrep fallback now inspects each match's full command line (pgrep -fl) and skips any process running a headless CLI job, so opening/relaunching the GUI no longer kills an in-flight --transcribe-file request.
  • Medium — Missing/empty model preference now falls back to .tiny (matching AppState's @AppStorage default) instead of failing fresh installs with model_not_found.
  • Low — Documented (README + inline comment) that translate-to-English and custom vocabulary are intentionally always off headlessly; only model and language follow app prefs.
  • Added test coverage for the --restarted dispatch case and the missing-preference fallback.

Validation: swift build passes; targeted CLITests (19/19) and the full non-hardware-dependent suite (281 tests) pass with 0 failures. AudioEngineDeviceChangeTests/AudioEngineForceResetTests were excluded from the full run — they deadlock in real CoreAudio in this sandboxed environment due to a pre-existing skipWithoutRealAudioInput() gap unrelated to this PR.

@jatinkrmalik jatinkrmalik added this to the v0.8.0 milestone Aug 12, 2026
@jatinkrmalik
jatinkrmalik merged commit 55da855 into VocaHQ:main Aug 13, 2026
5 checks passed
@jatinkrmalik

Copy link
Copy Markdown
Member

@Mr-Sunglasses I went through 4319eaf against the bot threads.

--restarted now goes to the GUI, missing model pref falls back to tiny, translate/vocab is documented as off, and the two tests you added cover those cases.

One leftover for a follow-up, since this already merged: single-instance is only half-fixed. pgrep -fl skip is there, but ensureSingleInstance still NSRunningApplication.terminate()s every other com.vocamac.app PID with no CLI check. The documented CLI path is the app binary, so it has that bundle ID. The skip itself is commandLine.contains("-h"), which will treat a GUI binary under a path like vocamac-hotfix as a CLI job and leave a second menu bar running.

Related: Finder / Spotlight / open without -n will activate the in-flight CLI process instead of starting the GUI, because it is the same app. pgrep never gets a chance there.

Mind opening a follow-up for that? Match argv tokens, and only --transcribe-file / --list-models. --help / -h exit immediately and don't need protection. Worth a test too. CLITests never touches ensureSingleInstance, which is how the last bug landed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app docs enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants