Local-first, open-source apps
One folder of plain text and SQLite on your machine, synced across all your devices.
Grep it, query it, host it wherever you want.
Apps • Architecture • Packages • For Developers • Quick Start • Contributing • Discord
Epicenter is an ecosystem of open-source, local-first apps. Your notes, transcripts, and chat histories live in a single folder of plain text and SQLite on your machine. Every tool we build reads and writes to the same place. It's open, tweakable, and yours. Grep it, open it in Obsidian, version it with Git, host it wherever you want.
Under the hood, Yjs CRDTs are the single source of truth. They materialize down to SQLite (for fast queries) and markdown (for human-readable files). Sync happens over the Yjs protocol; the server is a relay, not an authority. It never sees your content.
The library that powers this, @epicenter/workspace, is something other developers can build on too. Define a typed schema, get CRDT-backed tables with multi-device sync handled for you.
Epicenter has three different backend boundaries on purpose:
Domains split by public protocol role.
Deployables split by infrastructure and operational boundary.
Hono modules split by code composition boundary.
Epicenter ships one shared server library and two deployables. The library is packages/server; the deployables compose it with different ownership rules:
apps/api hosted personal cloud (api.epicenter.so)
composes packages/server with personal()
bundles billing routes, dashboard SPA, Autumn policies
worker/ + ui/ deploy as one Cloudflare Worker
apps/self-host self-hosted shared wiki reference (community-supported)
composes packages/server with shared({ admit })
no billing surface, no dashboard, deployment-owned secrets
trust boundary: deployer holds ENCRYPTION_SECRETS
-> functionally zero-knowledge against Epicenter
Inside packages/server, route groups mount onto a shared Hono app via
mountSessionApp, mountRoomsApp, mountAssetsApp, mountAiApp, and authApp.
The personal() and shared({ admit }) factories choose the partition rule;
everything else is shared. The hosted-only billing code (packages/billing's
old contents, plus /api/billing/* routes and the dashboard SPA) lives inside
apps/api/worker/billing/ and apps/api/ui/. Self-hosted shared-wiki deployments
never see it.
┌──────────────────────────────────┐
│ @epicenter/api │
│ Cloudflare Workers + DO hub │
│ auth · sync relay · AI chat │
└──────────┬───────────────────────┘
│ y-websocket protocol
┌────────────────────┼──────────────────────┐
│ │ │
┌────▼──────┐ ┌─────▼────────┐ ┌──────▼──────┐
│ Whispering│ │ Opensidian │ │ Tab Manager │
│ (Tauri) │ │ (SvelteKit) │ │ (WXT ext) │
└────┬──────┘ └─────┬────────┘ └──────┬──────┘
│ │ │
┌─────────┴────────────────────┴──────────────────────┘
│ All apps share these layers:
│
┌─────▼───────────────────────────────────────────────────────────┐
│ MIDDLEWARE / ADAPTERS │
│ @epicenter/svelte : Svelte integration, auth, persistence │
│ @epicenter/filesystem : POSIX file layer over Yjs │
│ @epicenter/skills : skill/reference tables │
└──────────────────────────────┬──────────────────────────────────┘
│
┌──────────────────────────────▼───────────────────────────────────┐
│ CORE │
│ @epicenter/workspace : typed schemas, Yjs CRDTs, extensions, │
│ E2E encryption, lifecycle, materializers │
│ @epicenter/sync : protocol encoding/decoding, V2 updates │
│ @epicenter/constants : app URLs, versions, shared config │
│ @epicenter/ui : shadcn-svelte component library │
│ @epicenter/cli : TypeBox→yargs CLI, auth/session APIs │
└──────────────────────────────────────────────────────────────────┘
The dependency flow is strict: core has zero upward dependencies, middleware only reaches into core, and apps compose both. @epicenter/workspace is the gravitational center; every middleware package and most apps depend on it. The sync server is a relay, not an authority; it never sees your content because encryption happens client-side before anything leaves the device.
Full architecture walkthrough → · Encryption design →
WhisperingPress shortcut, speak, get text. Desktop transcription that cuts out the middleman. Bring your own API key or run locally with Whisper C++. |
OpensidianLocal-first note-taking with a built-in bash terminal, end-to-end encryption, and real-time sync. Your notes live in a CRDT-backed virtual filesystem. |
Tab ManagerBrowser extension side panel for managing tabs with workspace sync and AI chat that can call workspace tools with inline approval. |
HoneycrispApple Notes-style local-first notes app. Folders, rich-text editing with ProseMirror, and collaborative sync via Yjs. |
Epicenter APIThe hub server. Auth, real-time sync via Durable Objects, and AI inference. Everything that needs a single authority across devices. |
Build your ownThe |
Also in the repo: Fuji (personal CMS), Zhongwen (Mandarin learning chat), Skills Editor (agent skill manager), and Landing (public site).
| Package | Description | License |
|---|---|---|
@epicenter/workspace |
Core library. Typed schemas, Yjs CRDTs, extension builder, E2E encryption, materializers. Everything builds on this. | MIT |
@epicenter/sync |
Yjs sync protocol encoding/decoding. Dumb server, smart client; protocol framing is separate from transport. | MIT |
@epicenter/ui |
shadcn-svelte component library shared across all apps. | MIT |
@epicenter/svelte |
Svelte 5 integration: persisted state, auth UI, and workspace gates. | AGPL-3.0 |
@epicenter/filesystem |
POSIX-style virtual filesystem over Yjs workspace tables. mkdir, mv, rm, stat. |
MIT |
@epicenter/skills |
Skill and reference tables for AI-enhanced workspace apps. | AGPL-3.0 |
@epicenter/cli |
The epicenter command. TypeBox schemas become CLI flags automatically. |
AGPL-3.0 |
@epicenter/constants |
Shared URLs, ports, and version info across the monorepo. | AGPL-3.0 |
@epicenter/auth |
Framework-agnostic auth core. Imperative subscription API over better-auth. | AGPL-3.0 |
The hard problem with local-first apps is synchronization. If each device has its own SQLite file, how do you keep them in sync? If each device has its own markdown folder, same question. We ended up using Yjs CRDTs as the single source of truth, then materializing that data down to SQLite (for fast SQL reads) and markdown (for human-readable files). Yjs handles the sync; SQLite and markdown handle the reads.
The @epicenter/workspace package wraps this into a single API. Define a schema, get CRDT-backed tables, attach providers to materialize to SQLite or markdown, and add sync when you're ready.
import {
attachIndexedDb,
column,
createWorkspace,
defineTable,
openCollaboration,
roomWsUrl,
} from '@epicenter/workspace';
const posts = defineTable({
id: column.string(),
title: column.string(),
published: column.boolean(),
});
function openBlog(id: string, ownerId, deviceId, auth) {
const workspace = createWorkspace({
id,
tables: { posts },
kv: {},
});
const idb = attachIndexedDb(workspace.ydoc);
const collaboration = openCollaboration(workspace.ydoc, {
url: roomWsUrl({ baseURL: auth.baseURL, ownerId, guid: workspace.ydoc.guid, deviceId }),
openWebSocket: auth.openWebSocket,
onReconnectSignal: auth.onStateChange,
waitFor: idb.whenLoaded,
actions: {},
});
return { ...workspace, idb, collaboration };
}
const workspace = openBlog('epicenter-blog', myOwnerId, 'browser-dev', auth);
workspace.tables.posts.set({ id: '1', title: 'Hello', published: false });
Each user gets their own database. Schema definitions are plain JSON, so they work with MCP and OpenAPI out of the box. Write to Yjs and SQLite updates; edit a markdown file and the CRDT merges it in.
Read the full workspace docs →
More apps are in progress. Each one shares the same workspace, so data flows between them without import/export. The @epicenter/workspace library handles the hard parts (schemas, CRDT sync, materialization), so each new app is mostly UI.
Epicenter Cloud will provide hosted sync for people who don't want to run their own server. Same model as Supabase selling hosted Postgres or Liveblocks selling hosted collaboration. Self-hosting is and will remain first-class. The sync server is open source under AGPL, and when you run it yourself, you control the encryption keys and trust boundary.
brew install --cask whispering
Or download directly from GitHub Releases for macOS (.dmg), Windows (.msi), or Linux (.AppImage, .deb, .rpm).
# Prerequisites: Bun, local Postgres, and Infisical access for API secrets
git clone https://github.com/EpicenterHQ/epicenter.git
cd epicenter
bun install
bun dev
Root bun dev starts one local workflow: the API and Tab Manager. See
apps/api/README.md for local Postgres and Infisical
setup. Use bun run dev:api for only the local API, or
bun run dev:tab-manager:ui for only the extension UI. App folders still
support bun dev for focused local work on that app. Rust is only needed for
Tauri apps like Whispering.
If things break after switching branches or pulling changes:
bun clean # Clears caches and node_modules
bun install # Reinstall dependencies
For a full reset including Rust build artifacts (~10GB, takes longer to rebuild):
bun nuke # Clears everything including Rust target
bun install
You rarely need bun nuke. Cargo handles incremental builds well. Use bun clean first.
Most CLI commands default to the hosted Epicenter API at https://api.epicenter.so. If you are iterating on apps/api and want the CLI pointed at your local server, use the cli:local script:
bun run cli:local auth login
See packages/cli/README.md for the full environment table and per-host token file behavior.
We're looking for contributors who are passionate about open source, local-first software, or just want to build with Svelte and TypeScript.
Contributors coordinate in our Discord.
We publish our implementation specs. These are the reasoning behind non-obvious architectural choices: alternatives considered, trade-offs made, and why we landed where we did.
| Spec | What it decided |
|---|---|
| Encrypted Workspace Storage | XChaCha20-Poly1305 at the CRDT value level; server-managed keys with self-hosting as the trust boundary |
| Y-Sweet Persistence Architecture | How Yjs documents persist and compact in Durable Objects |
| Simple Definition-First Workspace API | The defineTable → createWorkspace + attach* composition pattern |
| Resilient Client Architecture | How workspace clients handle offline, reconnect, and extension failures |
| Migrate to @epicenter/sync | Custom sync protocol replacing Y-Sweet with our own framing layer |
All 112 implemented specs live in specs/.
Epicenter uses a sharp two-tier split:
@epicenter/workspace, @epicenter/ui, @epicenter/filesystem, and @epicenter/sync. An external developer can npm install any of these and embed them in a closed-source product; the toolkit's dependency closure is entirely MIT, enforced by bun run check:licenses.This follows the same pattern as Plausible and PostHog (AGPL apps and servers, hosted SaaS as revenue), and Yjs (MIT core library, AGPL y-redis server).
See the root LICENSE for the full index, FINANCIAL_SUSTAINABILITY.md for the narrative, and specs/20260428T120000-licensing-strategy.md for the threat model and decision procedure.
Contact: [email protected] | Discord | @braden_wong_
Local-first · CRDT · Own your data · Open source