Omnidesk is a local-first desktop application foundation for a unified inbox across Teams, Slack, and IMAP/SMTP accounts.
The current scaffold is intentionally provider-ready rather than provider-complete: the app boots, owns a secure Electron boundary, initializes SQLite, exposes typed IPC, and renders a premium dark-first shell with empty states.
- Electron with context isolation enabled
- TypeScript in strict mode
- Vite via
electron-vite - Vue 3 and Pinia
- Tailwind CSS
- SQLite via
libsql(N-API, ABI-stable) - Secret storage via Electron's
safeStorage - ESLint and Prettier
- pnpm workspaces
apps/desktop
src
main Electron main process, database, IPC, providers, sync
preload Secure context bridge exposed to the renderer
renderer Vue application shell and UI primitives
shared Cross-process types, IPC contracts, models, safe errors
pnpm install
pnpm dev
pnpm build
pnpm lint
pnpm typecheck
pnpm --filter @omnidesk/desktop rebuild:native
pnpm --filter @omnidesk/desktop packagerebuild:native (alias for electron-builder install-app-deps) rebuilds native modules
after a fresh install. Because libsql ships ABI-stable N-API prebuilt binaries and
secrets use Electron's built-in safeStorage, changing the Electron version no longer
requires a rebuild.
Omnidesk se met a jour via electron-updater.
L'app verifie les MAJ au lancement (puis toutes les 6 h) et telecharge en arriere-plan.
Quand une version est prete, un indicateur apparait a cote de la cloche (une fleche qui
descend) : un clic redemarre l'app pour l'installer. Une notification systeme previent
aussi lorsque la fenetre est masquee.
Apres une mise a jour, une modal Nouveautes s'affiche une seule fois : elle reprend les
sections de apps/desktop/CHANGELOG.md plus recentes que la
version precedente. Pensez donc a ajouter une section a chaque release.
Le workflow .github/workflows/release.yml s'occupe de
tout sur un tag v* : il construit les artefacts par OS et les publie sur la GitHub
Release du tag (dmg/zip, exe NSIS, AppImage + les manifestes latest*.yml lus par les
clients). La version du tag doit correspondre a celle de apps/desktop/package.json.
main est protege (push direct interdit), donc le bump passe par une PR :
# 1. sur une branche : ajouter une section ## [0.2.0] - AAAA-MM-JJ dans
# apps/desktop/CHANGELOG.md + bumper "version" dans apps/desktop/package.json
# 2. ouvrir la PR et la merger dans main
# 3. taguer le commit sur main et pousser le tag (c'est le tag qui declenche la release)
git tag v0.2.0
git push origin v0.2.0Le depot doit etre public pour qu'electron-updater lise les releases sans jeton. La signature/notarisation macOS exige les secrets
CSC_LINK,CSC_KEY_PASSWORD,APPLE_ID,APPLE_APP_SPECIFIC_PASSWORDetAPPLE_TEAM_ID(voir l'en-tete du workflow).
Pour relire une release avant diffusion, passer releaseType de release a draft dans
build.publish (apps/desktop/package.json) et publier la release manuellement.
contextIsolation: truenodeIntegration: falsesandbox: true- Renderer talks only through
window.omnidesk - IPC payloads are validated with Zod
- Provider tokens never cross into the renderer
- External navigation is denied inside the app window and opened externally
SQLite is initialized under Electron's userData directory with WAL enabled. The
schema includes:
providersaccountsconversationsconversation_participantsmessagesattachmentssync_cursorsnotificationslocal_message_states
All domain rows use UUID-compatible text primary keys.
Providers implement CommunicationProvider and are registered in the main process.
- IMAP/SMTP (real)
- Slack et Teams en mode web (webview persistante, comptes "webpage")
The abstraction is ready for authentication, conversation sync, message sync, send, attachments, notifications, and cursor persistence.
L'integration IMAP/SMTP est native : aucune cle .env, tout est par compte. Vous pouvez ajouter plusieurs comptes mail (un par adresse).
- Dans Omnidesk, Ajouter un compte -> IMAP / SMTP.
- Saisissez votre adresse mail et votre mot de passe (ou mot de passe d'application si votre fournisseur le demande, comme Gmail ou iCloud).
- Omnidesk detecte automatiquement les serveurs en combinant :
- la base Mozilla autoconfig hebergee par Thunderbird,
- les fichiers autoconfig publies par votre fournisseur,
- les enregistrements DNS SRV
_imaps._tcp/_submissions._tcp(RFC 6186), avec un repli sur les MX du domaine.
- Verifiez/ajustez les serveurs IMAP et SMTP proposes, puis Connecter.
- Omnidesk teste la connexion (IMAP + SMTP) avant de persister le compte.
Le mot de passe est chiffre via le
safeStoraged'Electron (dont la cle maitre est gardee dans le trousseau du systeme) puis conserve en base locale chiffree.
Les fils de discussion sont reconstitues a partir des entetes Message-ID,
In-Reply-To et References. Les reponses reutilisent automatiquement le
sujet, la chaine References et les destinataires de la conversation.
Copy apps/desktop/.env.example when real credentials are introduced:
cp apps/desktop/.env.example apps/desktop/.envL'omnichat (appels audio/video, cf. plus bas) delègue ses secrets a un backend léger, OmniProxy (depot separe : github.com/n-ramos/omniproxy), pour ne JAMAIS embarquer de cle dans l'app de bureau : OmniProxy mint les jetons LiveKit et pilote l'enregistrement. Sans OmniProxy configure, l'omnichat est simplement desactive.
git clone https://github.com/n-ramos/omniproxy.git ../OmniProxy
cd ../OmniProxy
pnpm install
cp .env.example .env # renseigner OMNIPROXY_API_KEY, LIVEKIT_*, S3_* ...
docker compose up -d # LiveKit + redis + egress + MinIO (necessaire pour l'omnichat)
pnpm dev # http://127.0.0.1:8787 ; curl .../health pour verifierPuis cote Omnidesk, dans apps/desktop/.env (mode comptes : seule l'URL est requise) :
OMNIDESK_PROXY_URL=http://127.0.0.1:8787La connexion se fait ensuite par compte (inscription / connexion email + mot de passe) directement dans l'app, depuis l'ecran OmniChat : plus aucune cle d'API ni jeton partage a configurer cote desktop. L'email du compte est aussi l'identite OmniChat (DM, presence, appels).
Voir le README d'OmniProxy pour le detail (endpoints, securite, infra LiveKit).
Slack et Teams ne sont plus des integrations OAuth : ils s'utilisent en mode web.
Dans Ajouter un compte, les raccourcis Slack et Teams creent un compte de type
webpage preconfigure (cf. apps/desktop/src/shared/webServices.ts) :
- Slack ->
https://app.slack.com/client - Microsoft Teams ->
https://teams.microsoft.com
Chaque service s'ouvre dans une webview persistante a la session isolee par compte ; l'authentification se fait directement dans la page, comme dans un navigateur. Aucun secret, jeton ni cle d'API a configurer cote app.
Omnichat ajoute des appels temps reel (audio, video, partage d'ecran et enregistrement) attaches a une conversation, via LiveKit. Les jetons LiveKit sont mintes par OmniProxy (voir plus haut) ; l'app n'embarque aucune cle LiveKit.
Prerequis : OmniProxy demarre (pnpm dev) avec l'infra LiveKit lancee
(docker compose up -d), et OMNIDESK_PROXY_URL defini cote desktop.
Utilisation :
- Ouvrez une conversation puis cliquez l'icone telephone dans l'en-tete.
- macOS demande l'autorisation micro/camera au premier usage (l'app les declare dans son Info.plist et ses entitlements). Le partage d'ecran demande l'autorisation Enregistrement de l'ecran du systeme.
- Dans l'overlay d'appel : couper le micro, activer la camera, partager l'ecran,
enregistrer (l'egress depose un MP4 dans le bucket MinIO
recordings), ou raccrocher. Reduire garde l'appel actif en arriere-plan.
Securite : le micro, la camera et la capture d'ecran ne sont accordes qu'au renderer de l'app ; les pages web embarquees (OmniBrowser) n'y ont jamais acces.
CSP : la connexion de signalisation LiveKit utilise
ws://localhost:*, deja autorise par la CSP du renderer (apps/desktop/src/renderer/index.html). Si vous pointezLIVEKIT_PUBLIC_URLailleurs que sur localhost, ajoutez l'originewss://correspondante auconnect-src.
Portee V1 : une room LiveKit par conversation (on rejoint la meme room en ouvrant l'appel depuis la meme conversation). La sonnerie / notification d'appel entrant entre utilisateurs necessite une couche de signalisation (WebSocket sur OmniProxy) et constitue l'etape suivante.