Skip to content

Commit 8139633

Browse files
committed
Fix macOS build
1 parent 2750e7b commit 8139633

11 files changed

Lines changed: 1022 additions & 902 deletions

File tree

.github/workflows/release.yml

Lines changed: 10 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,16 @@ on:
1212
workflow_dispatch:
1313

1414
permissions:
15-
contents: write # required for softprops/action-gh-release to create releases
15+
contents: write
1616

1717
jobs:
1818
# ---------------------------------------------------------------------------
1919
build-windows:
2020
name: Build Windows (x64)
2121
runs-on: windows-latest
2222
steps:
23-
- name: Checkout
24-
uses: actions/checkout@v4
25-
26-
- name: Setup Node.js
27-
uses: actions/setup-node@v4
23+
- uses: actions/checkout@v4
24+
- uses: actions/setup-node@v4
2825
with:
2926
node-version: '20'
3027
cache: 'npm'
@@ -34,10 +31,6 @@ jobs:
3431
working-directory: electron
3532
run: npm ci --legacy-peer-deps
3633

37-
- name: Bundle whisper.cpp binary + base model
38-
working-directory: electron
39-
run: npm run setup:whisper
40-
4134
- name: Build renderer + main
4235
working-directory: electron
4336
run: npm run build
@@ -52,8 +45,7 @@ jobs:
5245
shell: bash
5346
run: ls -la dist/electron-release/
5447

55-
- name: Upload artifact
56-
uses: actions/upload-artifact@v4
48+
- uses: actions/upload-artifact@v4
5749
with:
5850
name: echo-windows-x64
5951
path: dist/electron-release/*.exe
@@ -63,13 +55,10 @@ jobs:
6355
# ---------------------------------------------------------------------------
6456
build-macos-arm64:
6557
name: Build macOS (Apple Silicon)
66-
runs-on: macos-latest # GitHub's default macOS runner is arm64
58+
runs-on: macos-latest
6759
steps:
68-
- name: Checkout
69-
uses: actions/checkout@v4
70-
71-
- name: Setup Node.js
72-
uses: actions/setup-node@v4
60+
- uses: actions/checkout@v4
61+
- uses: actions/setup-node@v4
7362
with:
7463
node-version: '20'
7564
cache: 'npm'
@@ -79,10 +68,6 @@ jobs:
7968
working-directory: electron
8069
run: npm ci --legacy-peer-deps
8170

82-
- name: Bundle whisper.cpp binary + base model
83-
working-directory: electron
84-
run: npm run setup:whisper
85-
8671
- name: Build renderer + main
8772
working-directory: electron
8873
run: npm run build
@@ -96,67 +81,31 @@ jobs:
9681
- name: Show output
9782
run: ls -la dist/electron-release/
9883

99-
- name: Upload artifact
100-
uses: actions/upload-artifact@v4
84+
- uses: actions/upload-artifact@v4
10185
with:
10286
name: echo-macos-arm64
10387
path: dist/electron-release/*.dmg
10488
if-no-files-found: error
10589
retention-days: 14
10690

107-
# ---------------------------------------------------------------------------
108-
# Intel macOS — uncomment to ship an x86_64 build too.
109-
# macOS 13 is the last GitHub-hosted Intel runner image.
110-
# ---------------------------------------------------------------------------
111-
# build-macos-intel:
112-
# name: Build macOS (Intel)
113-
# runs-on: macos-13
114-
# steps:
115-
# - uses: actions/checkout@v4
116-
# - uses: actions/setup-node@v4
117-
# with:
118-
# node-version: '20'
119-
# cache: 'npm'
120-
# cache-dependency-path: electron/package-lock.json
121-
# - run: npm ci --legacy-peer-deps
122-
# working-directory: electron
123-
# - run: npm run build
124-
# working-directory: electron
125-
# - run: npx electron-builder --mac --x64 --publish never
126-
# working-directory: electron
127-
# env:
128-
# GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
129-
# - uses: actions/upload-artifact@v4
130-
# with:
131-
# name: echo-macos-x64
132-
# path: dist/electron-release/*.dmg
133-
# if-no-files-found: error
134-
# retention-days: 14
135-
136-
# ---------------------------------------------------------------------------
137-
# Collect all artifacts and publish them as a GitHub Release.
138-
# Only runs for tag pushes — manual runs leave the artifacts on the run.
13991
# ---------------------------------------------------------------------------
14092
release:
14193
name: Publish GitHub Release
14294
needs: [build-windows, build-macos-arm64]
14395
runs-on: ubuntu-latest
14496
if: startsWith(github.ref, 'refs/tags/')
14597
steps:
146-
- name: Download all artifacts
147-
uses: actions/download-artifact@v4
98+
- uses: actions/download-artifact@v4
14899
with:
149100
path: artifacts
150101

151102
- name: Flatten artifacts
152103
run: |
153104
mkdir -p release
154105
find artifacts -type f -exec mv {} release/ \;
155-
echo "--- release contents ---"
156106
ls -la release/
157107
158-
- name: Publish release
159-
uses: softprops/action-gh-release@v2
108+
- uses: softprops/action-gh-release@v2
160109
with:
161110
files: release/*
162111
draft: false

README.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
<p align="center">
1111
<a href="https://github.com/Kenshiin13/echo/releases"><img src="https://img.shields.io/github/v/release/Kenshiin13/echo?style=flat-square&color=3FA8E0" alt="Release"/></a>
1212
<img src="https://img.shields.io/badge/platform-Windows%20%7C%20macOS-1f2937?style=flat-square" alt="Platform"/>
13-
<img src="https://img.shields.io/badge/whisper.cpp-v1.8.4-A855F7?style=flat-square" alt="whisper.cpp"/>
13+
<img src="https://img.shields.io/badge/engine-onnxruntime--node-A855F7?style=flat-square" alt="onnxruntime-node"/>
1414
</p>
1515

1616
---
@@ -23,11 +23,10 @@
2323

2424
- **Global push-to-talk** — press a hotkey anywhere (default `F9`), speak, release. Transcript is pasted at your cursor.
2525
- **Voice activation (optional)** — hands-free mode powered by [Silero VAD](https://github.com/snakers4/silero-vad). Flip it on in Settings and Echo auto-transcribes each utterance as you speak. Ignores non-speech noise.
26-
- **Fully local transcription** — all audio and transcription stays on-device via [whisper.cpp](https://github.com/ggml-org/whisper.cpp). Your voice never leaves your machine.
27-
- **Automatic translation (optional)** — point Echo at a target language and it will translate each transcript via [DeepL](https://www.deepl.com/) before pasting. Skipped automatically when you're already speaking the target language. Audio still stays local; only the transcript text is sent.
28-
- **Low-latency transcription** — Whisper runs as a persistent `whisper-server` process with the model kept resident in memory, so every utterance skips cold-load overhead.
29-
- **GPU acceleration** — CUDA on Windows (NVIDIA), Metal on Apple Silicon, CPU fallback everywhere.
30-
- **Five model sizes** — from 75 MB (`tiny`) to 1.6 GB (`large-v3-turbo`). Pick the accuracy/speed tradeoff you want.
26+
- **Fully local transcription** — Whisper runs in-process via [ONNX Runtime](https://onnxruntime.ai/) + [Transformers.js](https://huggingface.co/docs/transformers.js). Audio never leaves your machine.
27+
- **Automatic translation (optional)** — point Echo at a target language and it will translate each transcript via [DeepL](https://www.deepl.com/) before pasting. Audio stays local; only the transcript text is sent.
28+
- **Model kept resident** — loaded once at startup into the main process, reused for every utterance — no cold-load per transcription.
29+
- **Five model sizes** — from `tiny` to `large-v3-turbo`. Pick the accuracy/speed tradeoff you want.
3130
- **14 languages + auto-detect** — English, German, French, Spanish, Italian, Portuguese, Dutch, Russian, Chinese, Japanese, Korean, Arabic…
3231
- **Live audio indicator** — a small overlay shows a waveform while you speak, progress while a model downloads, and a check when paste completes.
3332
- **Model manager** — download, switch, and delete models from the settings window. New models download with a progress bar on first use.
@@ -40,18 +39,18 @@ Download the latest installer from the [Releases page](https://github.com/Kenshi
4039

4140
| Platform | File |
4241
|----------|------|
43-
| Windows 10/11 (x64) | `Echo-Setup-X.Y.Z.exe` |
44-
| macOS (Apple Silicon) | `Echo-X.Y.Z-arm64.dmg` |
42+
| Windows 10/11 (x64) | `Echo.Setup.X.Y.Z.exe` |
43+
| macOS (Apple Silicon — M1/M2/M3/M4) | `Echo-X.Y.Z-arm64.dmg` |
4544

4645
> **macOS note:** the build is unsigned (no paid Apple Developer ID). After dragging **Echo** to Applications, right-click the app → **Open** on first launch to bypass Gatekeeper.
4746
>
48-
> If macOS still refuses with *"Echo is damaged and can't be opened"*, it's the quarantine flag from the download. Clear it with:
47+
> If macOS still refuses with *"Echo is damaged and can't be opened"*, clear the quarantine flag:
4948
>
5049
> ```bash
5150
> xattr -cr /Applications/Echo.app && open /Applications/Echo.app
5251
> ```
5352
54-
On first launch, Echo will auto-download the selected whisper model (`base` by default, ~142 MB) with a progress indicator. If you have an NVIDIA GPU and select the **CUDA** backend, it will also download the CUDA-enabled binary.
53+
On first launch, Echo will auto-download the selected Whisper model (`base` by default) into `~/.../Echo/whisper-models` with a progress indicator.
5554
5655
## Usage
5756
@@ -77,8 +76,7 @@ Open settings from the tray icon. Everything is persisted to `electron-store` in
7776
| Push-to-talk hotkey | `F9` | Any key or modifier combo |
7877
| Exit shortcut | `Ctrl+Alt+Q` | Global quit |
7978
| Model size | `base` | `tiny` / `base` / `small` / `medium` / `large-v3-turbo` |
80-
| Language | Auto-detect | Pick one for better accuracy if auto-detect mis-fires |
81-
| Compute backend | Auto | `CPU` / `CUDA` / `MLX` — auto-selected based on hardware |
79+
| Language | Auto-detect | Pin one for slightly faster transcription + to enable the translate-skip optimization |
8280
| Auto-paste | On | Off = copy to clipboard only |
8381
| Voice activation | Off | Always-listening mode using Silero VAD (disables the hotkey) |
8482
| Translate transcription to | Off | Target language for automatic DeepL translation; skipped when you already speak it |
@@ -93,10 +91,11 @@ Requires Node.js 20+.
9391
git clone https://github.com/Kenshiin13/echo.git
9492
cd echo/electron
9593
npm install --legacy-peer-deps
96-
npm run setup:whisper # downloads whisper.cpp binary + base model
9794
npm run dev # dev mode with hot reload
9895
```
9996
97+
On first run Echo will download the selected Whisper ONNX model from Hugging Face.
98+
10099
Package an installer:
101100
102101
```bash
@@ -111,7 +110,7 @@ Releases are automated — pushing a `v*` tag (e.g. `v1.2.0`) triggers the [rele
111110
- **[Electron 33](https://www.electronjs.org/)** — shell
112111
- **[React 18](https://react.dev/) + [Vite 6](https://vite.dev/)** — renderer (three entries: settings, indicator overlay, audio capture)
113112
- **[Mantine v7](https://mantine.dev/) + [Tailwind](https://tailwindcss.com/)** — UI
114-
- **[whisper.cpp](https://github.com/ggml-org/whisper.cpp)**transcription engine, run as a long-lived `whisper-server` subprocess with the model kept resident in RAM
113+
- **[@huggingface/transformers](https://huggingface.co/docs/transformers.js)** + **[onnxruntime-node](https://onnxruntime.ai/)**Whisper ASR runs in-process using ONNX models from [Xenova/whisper-*](https://huggingface.co/Xenova)
115114
- **[Silero VAD](https://github.com/snakers4/silero-vad)** via [@ricky0123/vad-web](https://github.com/ricky0123/vad) — neural voice activity detection for the voice-activation mode
116115
- **[DeepL API](https://www.deepl.com/pro-api)** — optional cloud translation layer between Whisper and paste
117116
- **[koffi](https://koffi.dev/)** — FFI for global key polling on Windows

electron/electron-builder.config.js

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,32 @@ module.exports = {
1616
output: "../dist/electron-release",
1717
buildResources: "build-resources",
1818
},
19+
// Include the built output + runtime deps. electron-builder's default
20+
// patterns get replaced when `files` is set explicitly, so we list what
21+
// we need. Native .node binaries land where npm puts them under
22+
// node_modules/<pkg>/... and stay unpacked via `asarUnpack` below.
1923
files: [
2024
"dist/**",
2125
"!dist/renderer/**/*.map",
22-
// uiohook-napi is macOS/Linux only — exclude it from Windows builds
26+
"node_modules/**/*",
27+
"package.json",
28+
// uiohook-napi is macOS/Linux only — skip on Windows builds.
2329
...(process.platform === "win32" ? ["!node_modules/uiohook-napi/**"] : []),
30+
// Dev-only noise — keep slim but don't over-filter.
31+
"!node_modules/**/*.{md,map,ts,tsx}",
32+
"!node_modules/**/{test,tests,__tests__,example,examples,docs,.github}/**",
33+
"!node_modules/**/{LICENSE,license,LICENCE,licence,CHANGELOG,changelog,README,readme}{,.md,.txt,.markdown}",
2434
],
35+
// Native modules loaded via require() must sit on the real filesystem,
36+
// not inside app.asar. All three are standard prebuilt-.node packages.
2537
asarUnpack: [
26-
"node_modules/nodejs-whisper/**",
38+
"node_modules/onnxruntime-node/**",
2739
"node_modules/koffi/**",
2840
"node_modules/@nut-tree-fork/**",
41+
"node_modules/uiohook-napi/**",
2942
],
30-
// koffi ships pre-built Electron binaries — no native compilation needed.
31-
// uiohook-napi requires MSVC and is not used on Windows; skip it via beforeBuild.
43+
// Native modules need an Electron ABI rebuild on macOS/Linux. Windows
44+
// ships prebuilts for all of them; skip rebuild to save a CI step.
3245
npmRebuild: process.platform !== "win32",
3346
beforeBuild: async (context) => {
3447
if (context.platform.name === "windows") {
@@ -38,6 +51,9 @@ module.exports = {
3851
if (fs.existsSync(p)) fs.rmSync(p, { recursive: true, force: true });
3952
}
4053
},
54+
// Ad-hoc signs the macOS .app (no paid Apple Developer ID).
55+
// No-op on Windows/Linux.
56+
afterPack: "./scripts/mac-afterpack.js",
4157
extraResources: [
4258
{
4359
from: "../assets",
@@ -65,17 +81,13 @@ module.exports = {
6581
mac: {
6682
icon: "../assets/echo_macos_app_icon.icns",
6783
category: "public.app-category.productivity",
84+
// Apple Silicon only (M1/M2/M3/M4). Intel Macs are not supported.
6885
target: [
6986
{ target: "dmg", arch: ["arm64"] },
70-
{ target: "dmg", arch: ["x64"] },
7187
],
72-
// We don't have an Apple Developer ID, so we ship unsigned.
73-
// `hardenedRuntime: true` with a missing/invalid signature makes Gatekeeper
74-
// report the app as "damaged" with no right-click bypass — worse UX than
75-
// shipping plain unsigned. `identity: null` tells electron-builder to skip
76-
// signing entirely so Gatekeeper falls back to the normal "unidentified
77-
// developer" prompt that users can bypass with right-click → Open (and, in
78-
// the worst case, by clearing the quarantine xattr — see README).
88+
// Unsigned — we don't have a paid Apple Developer ID. afterPack does
89+
// an ad-hoc signing pass so Gatekeeper treats this as "unidentified
90+
// developer" rather than "damaged" (right-click → Open to bypass).
7991
identity: null,
8092
},
8193
dmg: {

0 commit comments

Comments
 (0)