The desktop client for neeme — a privacy-first, local-first personal memory system. Your personal AI, not a corporation's.
Built with Electron + Vite + React + Tailwind (TypeScript) via electron-vite.
pnpm install
pnpm dev # launch the app with HMR
pnpm build # typecheck + bundle all three processes
pnpm typecheck # types only (node + web)A pnpm-workspace + turborepo monorepo (see ADR 0006).
Run pnpm dev / pnpm build / pnpm typecheck / pnpm lint from the root — turbo fans out.
apps/desktop— the Electron app (this is the bulk of the repo).packages/contract—@mikan/contract: the backend⇄UI contract (IPC + view-model types) and the shared HTTP API client, consumed from source by the app (and a futureapps/mobileRN/Expo app).
mikan is local-first: your data lives on your device, not in the cloud.
- Three processes (electron-vite):
apps/desktop/src/main(Node/Electron — owns data),apps/desktop/src/preload(the securecontextBridge),apps/desktop/src/renderer(the React UI). Context isolation is on; the renderer never touches the database directly. - Data layer —
apps/desktop/src/main/dbuses Drizzle ORM over libSQL (a SQLite fork), as a plain on-devicefile:database in Electron'suserDatadir. libSQL is deliberate: the same driver later enables Turso embedded-replica sync without rewriting the data layer. - IPC seam —
packages/contract/src/ipc.tsdefines the typed contract (channel names + types) shared by all three processes. The renderer callswindow.api.memory.*, which routes through preload →ipcMain.handle→apps/desktop/src/main/services→ Drizzle.
Remote services (capture, search, todos) are served by the FastAPI backend in the sibling neeme repo (the backend + mobile app). The desktop app talks to it through a typed client generated from the backend's OpenAPI spec with @hey-api/openapi-ts — so client types can't drift from the server.
packages/contract/openapi.json— committed snapshot of the backend's OpenAPI spec.packages/contract/src/api/generated/**— generated SDK (committed; do not hand-edit).packages/contract/src/api/runtime.ts— base URL (VITE_NEEME_API_URL, defaulthttp://localhost:8000) + the auth-token seam. Survives regeneration.packages/contract/src/api/index.ts— the stable public surface callers import (@mikan/contract/api).
The client is plain fetch (no Electron/Node imports), so it's reusable as-is in a future React Native / Expo app.
# Regenerate the client after the backend's API changes:
cd ../neeme && .venv/bin/python scripts/export_openapi.py ../mikan/packages/contract/openapi.json
cd ../mikan && pnpm gen:api # root script → pnpm --filter @mikan/contract gen:api
# Run the backend locally so the app can reach it (uv-managed venv):
cd ../neeme && uv pip install -e ".[api]" && neeme serve # serves on :8000Today the renderer calls the API directly (plain
fetch, the same path RN will use). Once auth tokens exist, sensitive calls can move behind Electron main/IPC so tokens live insafeStorage— the env-agnostic client makes that a non-breaking change. Local libSQL data stays on the IPC path; HTTP is a separate concern.
- Sync — Turso embedded replicas; will only ever push end-to-end-encrypted data.
- Auth / at-rest encryption — currently Tier 1 (protected by the OS account, no app lock). Electron
safeStorage/ SQLCipher slot intoapps/desktop/src/main/dbwhen added. - Vector index (LanceDB / sqlite-vec) and enrichment, mirroring the broader neeme pipeline.
UI/UX is developed separately; the renderer here is a minimal functional placeholder.