Skip to content
EpicenterHQPublic

About

Open-source, local-first apps.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

4.8k stars

Watchers

18 watching

Forks

Latest commit

 

History

22,666 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Epicenter

Epicenter

Local-first apps over a store you own.

A store's whole data set is one CRDT document on your machine, complete enough to work with the network off. Sign in on a second device and the two converge. No server holds the only copy, and no app owns your storage.

Honeycrisp, a local-first notes app, is the app built on it today.

Run the apps freely under AGPL-3.0-or-later. What that means.

GitHub stars Apps license: AGPL-3.0 Packages license: AGPL-3.0-or-later Discord

The Store | Status | Trust | Repo Map | Development | License


The Store

The hard problem with local-first apps is synchronization. If each device has its own SQLite file, how do you keep them in sync?

Epicenter's answer: a database is one Yjs document, replayed in full before any handle exists, and the surface over it is synchronous. A read is a property access, not a round trip, so nothing is awaited and nothing needs cache invalidation or race protection. The rich half is in there too: a row's node is a nested type on the row, not a second document with an address of its own.

import { defineStore, defineTable, field, plainText } from '@epicenter/app';
import { openLocal } from '@epicenter/app/open';

const notesDefinition = defineStore({
	id: 'com.example.notes',
	kv: {},
	tables: {
		notes: defineTable({
			fields: {
				title: field.string(),
				pinned: field.boolean(),
				folderId: field.nullable(field.string()),
			},
			body: plainText(),
		}),
	},
});

// Opening resolves after storage acquisition and replay.
const data = await openLocal(notesDefinition);

const note = data.tables.notes.create({ title: 'Hello', pinned: false, folderId: null });

const listed = data.tables.notes.rows;             // synchronous flat rows
const stop = data.tables.notes.subscribe(() => { /* re-read rows */ });

// Before leaving the application:
stop();
await data.close();

A store definition describes durable data. openLocal acquires its Local store; openPersonal acquires a Personal store for an explicitly supplied account. Applications compose these stores with any other resources they need. Constructing a definition opens no storage and captures no account. It is release-local and never migrates your data. A row it cannot read is reported beside the rows it can, with the reason and the raw values intact, and an ordinary write repairs it.

The node at body merges per character. Declare its codec with body: plainText() and reach it with data.tables.notes.body(note.id); Epicenter never looks inside. The database document's kv/tables:<name> shape is recorded in ADR-0257 and its collapse to one document in ADR-0295.

Sync is one Cloudflare Durable Object per (account, application). Being signed in on two devices is the entire sharing model: nothing is paired, invited, or approved.

Read the application API | What it replaced, and why

Status

Store-backed apps run as a desktop SPA in a WebView, over a store the client owns. A host serves bundles and brokers credentials; it owns no application data. A hosted web runtime with a host-owned replica is refused, and so are third-party installed apps, for now.

Honeycrisp is a standalone desktop notes app over native Markdown files. Its rich-text editor keeps a local draft and undo history; saved files and ordinary Git hold the durable data.

Vocab, skills, and the Epicenter host now compile against the store. Whispering is a standalone desktop app over a Git-versioned folder of files, not the store. The superseded data stack was deleted before they were migrated, deliberately, so old data is not imported into the new model.

Matter edits user-owned Markdown folders directly and keeps a disposable matter.sqlite query mirror beside them. Local Books is a headless mirror that pulls a QuickBooks account into local SQLite. Local Mail is an Epicenter application with no CLI: it mirrors Gmail into local SQLite and reconciles from its own window. Those three do not use the store.

Trust Boundaries

Pick the trust model you want.

Path What leaves your device
Signed out Nothing. The store is complete on the machine it opened on, and every read comes from a document already in memory.
Signed in Your application's document, as opaque update bytes, to one authority per account.
Hosted Epicenter That authority is ours, along with account and session data and any hosted feature you enable.
Self-hosted instance You control the server, secrets, deployment, and infrastructure boundary.
A provider an app calls Whatever that app sends it: transcript text to an LLM, audio to a transcription provider. Epicenter servers are not in that path.

Signed-in sync sends your data to a trusted server that reads it in plaintext. On hosted Epicenter the authority is ours, so that data sits inside our trust boundary; self-hosting puts it on infrastructure you control, so Epicenter never holds it. See the trust model for the details, including where this is heading with the anchor.

Repo Map

Apps

App Status Notes
Honeycrisp Runs, development milestone Standalone Markdown notes. Metadata stays in frontmatter, logical folders preserve note identity, and Git records saved files.
Matter Runs, separately Typed grid over user-owned Markdown folders. It edits ordinary .md files directly; matter.sqlite is a disposable query mirror.
Local Books Runs, separately Headless CLI mirror that pulls QuickBooks into local SQLite.
Local Mail Runs, separately Gmail mirror with a triage window. No CLI; it reconciles in the foreground only.
API Hosted infrastructure Personal cloud Worker. Owns the store authority binding, hosted-only billing, and the dashboard.
Self-host Reference deployable Community-supported single-partition instance without hosted billing.
Whispering Runs, development milestone Standalone desktop recorder. Recordings, transcripts, and settings are files in one Git-versioned folder.
vocab, skills, Epicenter Compile Migrated onto the store.
Other app folders Research and prototypes Useful history and experiments, not the current product lineup.

Packages

These packages carry the main architecture.

Package Role License
@epicenter/app Application declarations and lifetime, with the store, persistence, and sync engine inside the package. AGPL-3.0-or-later
@epicenter/sqlite Neutral embedded-SQLite driver with Browser, Bun, and Durable Object adapters. It owns no product schema. AGPL-3.0-or-later
@epicenter/sync The WebSocket subprotocol vocabulary both halves of a handshake must agree on. AGPL-3.0-or-later
@epicenter/ui Shared Svelte component library used by multiple apps. AGPL-3.0-or-later
@epicenter/server Shared Hono server library composed by the hosted API and the self-host reference deployable. AGPL-3.0-or-later

Architecture

The server side is split into one shared library and two deployable folders:

packages/server
  shared Hono library
  route composition for auth, sessions, store sync, blobs,
  and provider-backed inference and transcription

apps/api
  hosted personal Cloudflare Worker
  composes packages/server with a Better Auth principal resolver
  owns hosted-only dashboard and billing code

apps/self-host
  self-hosted single-partition instance reference deployable
  composes packages/server with the instance principal resolver
  community-supported
  no hosted billing surface

Full architecture walkthrough | Trust model

Development

Use Bun in this repo.

git clone https://github.com/EpicenterHQ/epicenter.git
cd epicenter
bun install

Every app starts from the repo root. bun dev:<app> runs every process the app needs; for apps that talk to the hosted API, that includes the API worker on localhost:8787. bun dev:<app>:ui runs the app's frontend alone when that split exists, and bun dev:api runs just the backend. Bare bun dev is bun dev:honeycrisp, and bun run with no arguments lists every target.

Command Starts App port
bun dev:honeycrisp Standalone Honeycrisp desktop, no API Ephemeral loopback port
bun dev:honeycrisp:ui Honeycrisp in the browser, no API or Tauri shell 5175
bun dev:api Hosted API worker alone 8787
bun dev:api-dashboard API + dashboard UI 5178
bun dev:landing Landing site, standalone 4321
bun dev:matter Matter desktop, standalone 5180
bun dev:posthog-reverse-proxy PostHog reverse proxy Worker wrangler default
bun dev:self-host Self-host server (needs INSTANCE_TOKEN) 8787

bun dev:whispering runs the standalone Whispering desktop app (bun dev:whispering:ui runs its page in a browser tab). bun dev:vocab, bun dev:skills, and bun dev:epicenter still exist, and those apps compile against the store.

The API needs local Postgres and Infisical; see apps/api/README.md. Rust is needed for Tauri apps such as Honeycrisp and Matter. Local Books and Local Mail run their own multi-process dev flows; their READMEs document them.

bun run check is the gate. It runs lint, typecheck, every workspace test, and the structural checks, and it is the same gate CI runs, so a green local run predicts a green pull request. Formatting is handled separately by the autofix workflow.

bun run check

Run the pieces on their own while you work:

bun run format          # rewrite formatting (CI autofixes this for you)
bun run lint:check
bun run typecheck
bun run test
bun run check:structure # doc paths, catalog pins, API paths, licenses, UI boundary, boot purity

Two checks sit outside the gate on purpose. bun run check:doc-hygiene flags specs and ADRs that time has made stale, so it belongs to review rather than to merge. bun run smoke:local boots the API against local services.

Design Notes

Durable decisions and their reasoning live in docs/adr/. Specs in specs/ are in-flight design scaffolding rather than current truth; when a spec and an ADR disagree, the ADR wins. Start with docs/README.md.

Contributing

Contributions are welcome. Good entry points are docs, local-first infrastructure, Svelte interfaces, migrating a broken app onto the store, and small changes that make the repo easier to understand.

Read the Contributing Guide

Contributors coordinate in Discord.

License

Everything is AGPL-3.0-or-later. An MIT toolkit tier existed until 2026-08 and was dissolved; versions already published under MIT stay MIT for those versions.

See the root LICENSE, FINANCIAL_SUSTAINABILITY.md, and the licensing strategy for the full model.


Contact: github@bradenwong.com | Discord | @braden_wong_

Your data outlives the app that wrote it. Local-first, open source, built on Yjs.

About

Open-source, local-first apps.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

4.8k stars

Watchers

18 watching

Forks

Releases

Sponsor this project

Used by

Contributors

Languages