Skip to main content
This guide covers building OpenWhispr from source for development, testing, and distribution.

Prerequisites

  • Node.js 18+ and npm (nodejs.org)
  • Git for cloning the repository
  • Internet connection for downloading dependencies and binaries

Quick Start

1

Clone the repository

git clone https://github.com/OpenWhispr/openwhispr.git
cd openwhispr
2

Install dependencies

npm install
This installs both main process and renderer dependencies, including:
  • Electron 36
  • React 19, Vite, TypeScript
  • better-sqlite3 (requires native compilation)
  • ffmpeg-static, dbus-next, and more
3

Compile native helpers

npm run compile:native
Compiles platform-specific binaries:
  • macOS: macos-globe-listener (Swift), macos-fast-paste (C)
  • Windows: windows-key-listener.exe, windows-fast-paste.exe (C)
  • Linux: linux-fast-paste (C)
4

Download runtime binaries

npm run download:whisper-cpp
npm run download:llama-server
npm run download:sherpa-onnx
These are bundled with the app but not in Git (too large).
5

Run in development mode

npm run dev
Starts Vite dev server (port 5173) and Electron in development mode with hot reload.
Development mode uses NODE_ENV=development and loads from http://localhost:5173. Changes to React components hot-reload automatically.

Development Workflow

File Structure

openwhispr/
├── main.js                 # Electron main process entry
├── preload.js              # Context bridge (main ↔ renderer)
├── package.json            # Main dependencies + scripts
├── src/                    # React app (Vite project)
│   ├── App.jsx             # Main dictation UI
│   ├── main.jsx            # React entry point
│   ├── index.html          # HTML template
│   ├── vite.config.js      # Vite configuration
│   ├── components/         # React components
│   ├── helpers/            # Main process modules
│   ├── hooks/              # React hooks
│   ├── models/             # Model registry
│   ├── services/           # Business logic
│   └── dist/               # Vite build output (gitignored)
├── resources/              # Native source files
│   ├── bin/                # Compiled binaries (gitignored)
│   ├── globe-listener.swift
│   ├── windows-key-listener.c
│   └── linux-fast-paste.c
└── scripts/                # Build automation
    ├── download-whisper-cpp.js
    ├── build-globe-listener.js
    └── lib/download-utils.js

Development Scripts

# Start dev server + Electron with hot reload
npm run dev

# Run only Vite dev server (port 5173)
npm run dev:renderer

# Run only Electron (assumes Vite is running)
npm run dev:main

# Format code with ESLint + Prettier
npm run format

# Type-check TypeScript
npm run typecheck

Native Module Compilation

macOS Globe Key Listener

Source: resources/globe-listener.swift Build script: scripts/build-globe-listener.js
# Compile Swift to native binary
swiftc \
  -O \
  -o resources/bin/macos-globe-listener \
  resources/globe-listener.swift \
  -framework ApplicationServices \
  -framework Carbon
Requirements:
  • Xcode Command Line Tools installed
  • macOS 10.15+ for ApplicationServices APIs
If Swift compilation fails, the app falls back to standard hotkeys (no Globe/Fn key support).

Windows Key Listener

Source: resources/windows-key-listener.c Build script: scripts/build-windows-key-listener.js
# Compile with MSVC (preferred)
cl /O2 windows-key-listener.c /Fe:windows-key-listener.exe user32.lib

# Or MinGW-w64
x86_64-w64-mingw32-gcc -O2 windows-key-listener.c -o windows-key-listener.exe -luser32
Prebuilt binary: Downloaded from GitHub releases if compilation fails.

Linux Fast Paste

Source: resources/linux-fast-paste.c Build script: scripts/build-linux-fast-paste.js
# Compile with XTest support
gcc -O2 -o linux-fast-paste linux-fast-paste.c -lX11 -lXtst
Fallback: If compilation fails, app uses xdotool or wtype.

Building for Distribution

Unsigned Builds (Personal Use)

Best for testing or personal use without code signing:
# Build without signing (skips notarization)
CSC_IDENTITY_AUTO_DISCOVERY=false npm run pack
Output locations:
  • macOS: dist/mac-arm64/OpenWhispr.app (or dist/mac/ for Intel)
  • Windows: dist/win-unpacked/OpenWhispr.exe
  • Linux: dist/linux-unpacked/openwhispr
When opening an unsigned app, macOS shows a security warning.Workaround:
  1. Right-click OpenWhispr.app
  2. Select “Open”
  3. Click “Open” in the dialog
This only needs to be done once.

Signed macOS Builds

1

Obtain certificates

  1. Join Apple Developer Program ($99/year)
  2. Create “Developer ID Application” certificate
  3. Download and install in Keychain Access
  4. Export as .p12 file (optional, for CI)
2

Configure notarization

Create ~/.electron-builder-notarize.json:
{
  "appleId": "[email protected]",
  "appleIdPassword": "@keychain:AC_PASSWORD",
  "teamId": "YOUR_TEAM_ID"
}
Store password in Keychain:
xcrun notarytool store-credentials "AC_PASSWORD" \
  --apple-id "[email protected]" \
  --team-id "YOUR_TEAM_ID" \
  --password "app-specific-password"
3

Build with signing

npm run build:mac
This will:
  1. Build React app
  2. Package with electron-builder
  3. Sign all binaries and frameworks
  4. Create DMG and ZIP
  5. Notarize with Apple (10-30 minutes)
  6. Staple notarization ticket
4

Verify notarization

spctl -a -vv -t install dist/OpenWhispr-1.5.5.dmg
# Output: accepted
# source=Notarized Developer ID

Signed Windows Builds

1

Obtain code signing certificate

Purchase from DigiCert, Sectigo, or another CA.
  • EV certificate (recommended): Stored on USB token, instant SmartScreen reputation
  • Standard certificate: Cheaper but requires reputation building
2

Configure electron-builder

Export certificate as .pfx file with password.Set environment variables:
$env:CSC_LINK = "C:\path\to\certificate.pfx"
$env:CSC_KEY_PASSWORD = "certificate-password"
3

Build with signing

npm run build:win
Outputs:
  • dist/OpenWhispr-1.5.5-setup.exe (NSIS installer, signed)
  • dist/OpenWhispr-1.5.5-win.zip (portable, signed)

Linux Packages

npm run build:linux
Generates multiple package formats:
File: dist/OpenWhispr-1.5.5-linux-x64.AppImageSelf-contained, portable executable:
chmod +x OpenWhispr-1.5.5-linux-x64.AppImage
./OpenWhispr-1.5.5-linux-x64.AppImage

Multi-Platform Builds

Build for all platforms from a single host (requires Docker or VMs):
# macOS → Build for macOS, Windows, Linux
npm run download:whisper-cpp:all
npm run download:llama-server:all
npm run download:sherpa-onnx:all

cd src && vite build && cd ..

# macOS (native)
electron-builder --mac

# Windows (requires Wine or Windows VM)
electron-builder --win --x64

# Linux (requires Docker)
electron-builder --linux --x64
Cross-compilation limitations:
  • macOS → Windows/Linux: Requires Wine (Windows) or Docker (Linux)
  • Windows → macOS: Not possible (need macOS for signing)
  • Linux → macOS/Windows: Docker for Windows, impossible for macOS

Code Signing Considerations

File: resources/mac/entitlements.mac.plist
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<key>com.apple.security.device.audio-input</key>
<true/>
Required for:
  • allow-unsigned-executable-memory: V8 JIT in Electron
  • disable-library-validation: Native modules (better-sqlite3, ffmpeg)
  • audio-input: Microphone access
Signing adds:
  • Publisher name in installer
  • Removal of “Unknown publisher” warning
  • Faster SmartScreen reputation (with EV cert)
Note: Standard certs require downloads before SmartScreen trusts the app.
Not commonly used. Most users trust:
  • Repository packages: Official distro repos
  • Flatpak: Sandboxed with explicit permissions
  • AppImage: User verifies SHA256 checksum

Troubleshooting Build Issues

Symptom: Error: Cannot find module 'better-sqlite3'Fix:
# Rebuild native modules for current Electron version
npm run postinstall

# Or manually:
npm rebuild better-sqlite3 --build-from-source --runtime=electron --target=36.9.5
Symptom: TypeScript errors or missing modulesFix:
# Clear Vite cache
rm -rf src/.vite src/dist src/node_modules/.vite

# Reinstall dependencies
cd src
npm install
npm run build
Symptom: “whisper-cpp binary not found” at runtimeFix:
# Download binaries for current platform
npm run download:whisper-cpp

# Verify they're in resources/bin/
ls -la resources/bin/whisper-cpp-*
Common issues:
  1. Wrong certificate: Must be “Developer ID Application”, not “Mac Developer”
  2. Hardened runtime missing: Check electron-builder.jsonmac.hardenedRuntime: true
  3. Invalid entitlements: Verify entitlements.mac.plist syntax
  4. Apple ID password: Use app-specific password, not account password
Debug:
# Check notarization status
xcrun notarytool log <submission-id>
Symptom: “VCRUNTIME140.dll was not found” on some Windows machinesFix: Include Visual C++ Redistributable in installer
// electron-builder.json
"nsis": {
  "include": "resources/nsis/vcredist-check.nsh"
}

CI/CD Pipeline Example

name: Build macOS
on:
  push:
    tags:
      - 'v*'

jobs:
  build:
    runs-on: macos-13
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Compile native binaries
        run: npm run compile:native
      
      - name: Download runtime binaries
        run: |
          npm run download:whisper-cpp
          npm run download:llama-server
          npm run download:sherpa-onnx
      
      - name: Build app
        env:
          CSC_LINK: ${{ secrets.MAC_CERT_BASE64 }}
          CSC_KEY_PASSWORD: ${{ secrets.MAC_CERT_PASSWORD }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
        run: npm run build:mac
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: macos-build
          path: |
            dist/*.dmg
            dist/*.zip

Performance Optimization

1

Minimize bundle size

  • Exclude dev dependencies from production build
  • Use files array in electron-builder.json to exclude unnecessary files
  • Enable ASAR packaging (default)
2

Optimize React build

// vite.config.js
export default {
  build: {
    minify: 'terser',
    terserOptions: {
      compress: {
        drop_console: true,  // Remove console.logs in production
      },
    },
    rollupOptions: {
      output: {
        manualChunks: {
          vendor: ['react', 'react-dom'],
        },
      },
    },
  },
};
3

Lazy load components

const SettingsPage = lazy(() => import('./components/SettingsPage'));
const ControlPanel = lazy(() => import('./components/ControlPanel'));

Next Steps

Architecture

Understand the codebase structure

Model Registry

Learn how models are managed

Troubleshooting

Debug common build and runtime issues

Build docs developers (and LLMs) love