Development and builds
Run the desktop app in development against your local backend, then package per-OS installers with electron-builder.
Running in development
In dev the app reads the monorepo root .env - the same file the web app uses - so it targets whatever backend your architecture runs (a fullstack frontend serving /api on its own port, or a separate backend on its own port). With the web app running, start the desktop app with no extra env:
pnpm --filter desktop devThis runs electron-vite dev: it starts the renderer dev server with HMR, builds the main and preload bundles, and opens the native window.
- Overrides:
BASE_URL/PUBLIC_API_URLset in your shell still override the.envvalues (e.g. to point dev at a staging backend). - Renderer: loads from the electron-vite renderer dev server with hot-reload; the main process points the window at it via
ELECTRON_RENDERER_URL.
Building installers
Package a platform's installers with its build script - each runs electron-vite build then electron-builder:
pnpm --filter desktop build:mac # or build:win / build:linux| Platform | Bundles |
|---|---|
| macOS | .dmg (+ a .zip the updater consumes) |
| Windows | nsis (.exe) |
| Linux | .AppImage + .deb |
electron-builder targets the host OS, so build each platform's installer on that platform (or in CI). Output lands in apps/electron/dist/.
When AI ships, every packaged build first stages the companion into the app: pnpm run stage:companion (wired into each build:* script) builds the standalone companion and copies its vendored Node runtime plus the daemon into apps/electron/resources/companion/, which electron-builder bundles. The packaged app runs that bundled copy directly - there is nothing to install separately. A build with AI off skips this step and ships no companion.
- Signing: macOS code signing and notarization are secret-gated; Windows uses Azure Trusted Signing when configured, otherwise it builds unsigned - see Releasing.
- Packaging config:
apps/electron/electron-builder.mjs(targets,asarUnpack,files, the macOS/Windows/Linux blocks). The app identity -appId,productName, deep-link scheme, update feed - is read straight from@repo/config/desktop, so the installer and the running app can never disagree.
The in-app terminal uses a native node-pty binding. electron-builder rebuilds it to Electron's ABI in its beforePack hook (the build has npmRebuild off), so the packaged binding always matches the Electron runtime across platforms and arches - a belt-and-suspenders step, not a manual one.
To rebrand the app icon, replace apps/electron/build/icon.png with your own 512x512+ PNG; electron-builder generates the per-OS icons (.icns, .ico) from it at build time.
Organizations
How the desktop app honors config.tenancy - organization switching, role-gated management, and per-entity entitlements.
Releasing
Build, sign, notarize, and publish the desktop app from GitHub Actions - automatically after CI passes on main, or manually - with storage and signing secrets and an electron-updater feed.