Desktop app
The cross-platform Electron desktop app gated by config.desktop - what it ships, how to enable it, and how AI and billing behave inside it.
A cross-platform Electron app for desktop-first products, gated by config.desktop and shipped off by default. The native app is your primary surface; the web frontend keeps marketing, purchase, authentication, and billing.
| Location | apps/desktop |
| Stack | Electron + React 19 + Vite (electron-vite) + shadcn/ui on Base UI |
| Theme | shared @repo/styles |
| Main process | apps/desktop/src/main - native window, deep links, updates, secure storage |
| Renderer | apps/desktop/src/renderer - the UI, reaching main only through the validated window.api preload bridge |
The desktop app requires a Pro (or Agency) license. Solo licenses cannot scaffold apps/desktop: the CLI hides the option and the license server rejects it.
Enabling it
Scaffold with the --desktop flag on the init command. It defaults off; --no-desktop is the explicit off.
generatesaas init --desktop| Piece | --desktop on | --desktop off |
|---|---|---|
apps/desktop (Electron main + React renderer) | kept | stripped |
bearer plugin in @repo/auth | kept | stripped |
/auth/device verification page + deviceAuthorizationClient wiring | kept | stripped |
deviceAuthorization plugin in @repo/auth | kept | kept (inert) |
config.desktop block in @repo/config | enabled: true | kept, enabled: false |
The off state keeps the inert pieces - the deviceAuthorization plugin, the deviceCode table, the device.* i18n keys, the config.desktop block - because they are additive and harmless without the app. See Configuration for the generated block.
What the app includes
The AI init answer decides what the app is:
| Build | What you get |
|---|---|
| Desktop + AI | The local AI stack (Chat, Automations, the AI settings screens), the Terminal that runs the user's own coding CLI, and the agent runtime the app forks from its own binary. A run goes to your hosted default or to the user's own CLI, whichever their default resolves to. |
| Desktop without AI | A client shell - login, onboarding, home, account, settings, organization, and notifications (bell, list and detail) - wired to your backend over device sign-in. The AI surface still ships, held off by config.desktop.agents.enabled: false, so you can turn it on later without re-scaffolding. |
The app follows the same default model as the web app: a fresh chat runs on your hosted default and meters credits, exactly as the website does, unless the user has picked their own. Set config.desktop.agents.defaultModel to a CLI for the local-first posture - then every default run happens on that user's own machine, under their own subscription, and costs you nothing. Either way a user can pick a connected CLI for themselves at any time, and integrations stay in your backend so one connection serves every surface.
The default is a pointer, not a copy: a user who never chose, or who used the Models screen's reset, follows whatever default your next build ships.
On macOS the app draws its own title bar: the top 38px drag the window and hold the side-panel toggle, and they drop in full screen. A control you place in that band needs the app-no-drag class, or clicks on it move the window instead. Windows and Linux keep the native frame.
Entitlements and billing
The app reads the session plan and credits and can gate features behind them. The credit surfaces (sidebar balance pill, Account credits row) show when this app can spend credits: credits configured and the desktop AI surface on.
- Source: the pure-data
@repo/config/pricingexport plusapps/desktop/src/renderer/lib/entitlements.ts. - Checkout stays on the web: the in-app Billing screen shows the plan, credits and who funds the workspace; changing plan, buying credits and the billing portal open
/settings/billingin the browser. - Transactions and Developers are in-app: the Transactions screen (with
config.payment.enabled) lists the billing history, and the Developers screen (withconfig.apiKeys.enabled) creates and revokes API keys without leaving the app. - The admin panel is in-app too: all eight tabs - the finance dashboard, users, organizations, audit logs, billing logs, API keys, announcements and email analytics - render the same shared bodies the web does, behind the same platform
adminrole. A non-admin gets no route, no sidebar entry, no palette command and no tab. Starting an impersonation stays a web action; the warning banner mounts here regardless, and draws itself only if a session ever carries one. - Profile does not: the same screen edits photo, name, country, phone, and marketing consent over
authClient. The photo is set by URL - the renderer's bridge serializes request bodies to text, so it cannot upload a file. - Neither does security: the Security screen beside it changes the password, manages TOTP two-factor, and revokes sessions. See Account security settings.
Credits survive the app being local-only. The model turn is paid for by the user's own CLI subscription, but you can meter anything else your product does on their behalf - an external API you pay for, say - against the same balance. Ship credits off in config.pricing to remove the surface entirely.
Next steps
Sign-in and security
Device authorization sign-in, CSP, the preload bridge, and the OS keychain.
Organizations
How the app honors config.tenancy - switching, role-gated management, owner-funded work.
Projects
Per-project on-device data, and connecting a real folder for chats and automations.
AI agents
Turn the user's own AI subscription into a workspace-scoped agent.
Agent runtime
The runtime the app forks from its own binary - nothing to install or pair.
Development and builds
Run against your local backend, then package per-OS installers with electron-builder.
End-to-end tests
Drive the built app with Playwright against a local backend, on macOS or an Xvfb runner.
Releasing
Build, sign, notarize, and publish installers from GitHub Actions.
Updates and downloads
Background auto-updates via electron-updater and always-latest download links.