From f74536e5cd43237a6105328ba0533d36fbd34e75 Mon Sep 17 00:00:00 2001 From: TerrifiedBug Date: Fri, 4 Sep 2026 00:55:57 +0100 Subject: [PATCH 1/9] Make the app the product, not the CLI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit yap shipped as a CLI that happened to draw a menu bar. Onboarding was `yap setup`, the login item was `yap install`, a missing grant printed into a log file nobody reads, and the .app existed only so the build could be notarized. This turns that around: the .app is the product and the CLI keeps the two things a terminal is genuinely better at. - The CLI is `run` and `bench`. `setup`, `install`, `start`, `stop`, `doctor`, `record` and `models` are gone, along with every `run` flag the Settings window already covers. - The status item goes up before the model loads, so a first run is visible for the minutes it spends downloading instead of invisible. A press during that says so rather than recording into nothing. - Onboarding is in the menu: rows for Accessibility, the microphone and a hijacked Fn key, each one click from the right Settings pane. The daemon no longer exits when Accessibility is missing — it watches for the grant and starts the tap the moment it lands, no relaunch. - Settings gains a General pane: launch at login, updates, logs. - The hotkey is recorded rather than picked from five options. Any modifier held alone, or a chord like cmd+shift+space, which is swallowed so the app underneath never sees it. - The transcript pill gains Delete, to the Trash, with Undo. - yap updates itself from GitHub Releases: checksum, then a signature check against the team the running code is signed by, then a menu item that is never offered mid-recording. The press now acknowledges itself before the mic opens, which is the one latency change: pill on screen 1.9 ms after key-down (p50 of 10) against 69.7 ms before, because it used to wait out Core Audio. Transcription is untouched — 39 ms p50 on a 5 s clip, on the number. A 0.2 login item passes --skip-doctor, a flag that no longer exists. That argument is ignored and the plist rewritten on the first start, or launch-at-login would have become a relaunch loop on upgrade. --- .github/workflows/ci.yml | 2 +- .github/workflows/release.yml | 35 +- README.md | 104 +- Sources/yap/Agent.swift | 189 ---- Sources/yap/Config.swift | 23 +- Sources/yap/Daemon.swift | 848 +++++++++++++++ Sources/yap/Doctor.swift | 335 ------ Sources/yap/Input/HotkeyBinding.swift | 239 +++++ Sources/yap/Input/HotkeyMonitor.swift | 233 +++-- Sources/yap/Install.swift | 160 --- Sources/yap/LaunchAgent.swift | 157 +++ Sources/yap/Models/ModelRegistry.swift | 3 - Sources/yap/Models/TranscriptionModel.swift | 18 - Sources/yap/Paths.swift | 15 + Sources/yap/Permissions.swift | 86 ++ Sources/yap/Setup.swift | 159 --- .../TranscriptionCoordinator.swift | 80 +- Sources/yap/UI/HotkeyRecorder.swift | 225 ++++ Sources/yap/UI/MenuBarController.swift | 171 ++- Sources/yap/UI/PromptPanel.swift | 38 +- Sources/yap/UI/RecordingOverlay.swift | 8 + Sources/yap/UI/SettingsModel.swift | 74 +- Sources/yap/UI/SettingsPanes.swift | 45 +- Sources/yap/UI/SettingsWindow.swift | 12 +- Sources/yap/UI/StatusIcon.swift | 27 +- Sources/yap/Updater.swift | 405 +++++++ Sources/yap/Yap.swift | 990 ++---------------- Tests/yapTests/HotkeyBindingTests.swift | 102 ++ Tests/yapTests/HotkeyMonitorTests.swift | 154 +++ scripts/build-release.sh | 8 + scripts/dev-install.sh | 123 +-- 31 files changed, 2921 insertions(+), 2147 deletions(-) delete mode 100644 Sources/yap/Agent.swift create mode 100644 Sources/yap/Daemon.swift delete mode 100644 Sources/yap/Doctor.swift create mode 100644 Sources/yap/Input/HotkeyBinding.swift delete mode 100644 Sources/yap/Install.swift create mode 100644 Sources/yap/LaunchAgent.swift create mode 100644 Sources/yap/Permissions.swift delete mode 100644 Sources/yap/Setup.swift create mode 100644 Sources/yap/UI/HotkeyRecorder.swift create mode 100644 Sources/yap/Updater.swift create mode 100644 Tests/yapTests/HotkeyBindingTests.swift create mode 100644 Tests/yapTests/HotkeyMonitorTests.swift diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 311a2a8..5f2aaf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,7 +46,7 @@ jobs: run: | BIN=.build/arm64-apple-macosx/release/yap "$BIN" --help - "$BIN" models list + "$BIN" --version "$BIN" bench --help # The embedded __TEXT,__info_plist is what lets TCC attribute the diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9dc3db1..e1dc0b5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -106,9 +106,12 @@ jobs: id: pkg working-directory: dist run: | - DMG="yap-${{ steps.v.outputs.version }}.dmg" - shasum -a 256 "$DMG" > "$DMG.sha256" - echo "sha256=$(shasum -a 256 "$DMG" | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" + V="${{ steps.v.outputs.version }}" + shasum -a 256 "yap-$V.dmg" > "yap-$V.dmg.sha256" + # The in-app updater fetches this one and compares it against the zip + # before it unpacks anything. + shasum -a 256 "yap-$V.zip" > "yap-$V.zip.sha256" + echo "sha256=$(shasum -a 256 "yap-$V.dmg" | cut -d' ' -f1)" >> "$GITHUB_OUTPUT" - name: Publish release uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 @@ -116,6 +119,8 @@ jobs: files: | dist/yap-${{ steps.v.outputs.version }}.dmg dist/yap-${{ steps.v.outputs.version }}.dmg.sha256 + dist/yap-${{ steps.v.outputs.version }}.zip + dist/yap-${{ steps.v.outputs.version }}.zip.sha256 generate_release_notes: true fail_on_unmatched_files: true @@ -151,10 +156,10 @@ jobs: depends_on macos: :sequoia app "yap.app" - # yap is a CLI first. The bundle exists so the build can be - # notarized and so TCC has something stable to hang grants on; this - # puts the command itself on PATH. - binary "#{appdir}/yap.app/Contents/MacOS/yap" + # No "binary" stanza. yap is a menu-bar app; the only subcommand + # left is "bench", which is a development tool, and putting a + # second path to the same executable on PATH is how the daemon + # ended up with two LaunchServices identities in the first place. # Homebrew replaces /Applications/yap.app, but a process keeps the # image it already mapped. Without this you stay on the old version @@ -188,11 +193,11 @@ jobs: # ~/Library/LaunchAgents/