A single-user personal dashboard for projects, learning, and notes with frictionless capture from web, CLI, mobile (PWA), and a global hotkey.
V2 PRD · Implementation PRD · Product PRD · Design spec · Architecture decisions
Everything below in v1, plus: milestones with derived progress (and seeding from the in-app
PRD editor), first-class notes (edit/delete anywhere + /notes), explicit
archive/restore, tag management (rename/merge/delete), a kanban board over project
stages, the curiosity → learning goal promotion flow, build-run queueing with a dashboard
overnight summary (webhook accepts human project slugs; scripts/simulate-run.ts stands in
for the agent), a global /activity feed with cursor pagination, real PWA icons, and a
motion pass across the UI. Status + verification: BUILD_STATUS.md.
Capture from any surface lands in a unified Inbox. From there it's filed into a Project (with lifecycle stages + snooze) or a Learning Goal (with checklists + a reading list). The Dashboard surfaces momentum (last 7 days touched) and stalls (configurable per entity, default 3d for projects / 2d for goals). A Telegram integration delivers a daily digest and stall alerts; quiet hours respected.
The agentic-coding workflow contract is designed and stubbed (POST /webhooks/v1/runs/events with bearer + HMAC + idempotency); when an agent comes online, no code changes needed.
The three demoable flows (PRD §1.3) are the v1-done bar:
- Capture from web → file from Inbox → Dashboard reflects momentum + stall clock.
- Capture from CLI → that evening's daily Telegram digest fires.
curlPOST a sample run event →activity_eventwritten → Dashboard reflects the run.
Requires Node 22+, pnpm 9+, macOS or Linux (Windows-friendly server-side; Tauri helper is mac-only for week 1).
corepack enable
pnpm install
# Copy env and edit secrets (at minimum: COMPASS_SESSION_SECRET, COMPASS_WEBHOOK_HMAC_SECRET)
cp .env.example .env.local
# Apply migrations + seed the settings singleton
pnpm db:migrate
# To create the first user, set this env var before signing in (bootstrap is opt-in):
echo "COMPASS_BOOTSTRAP_ALLOW_FIRST_USER=1" >> .env.local
# Run the web app — http://localhost:3000
pnpm webVisit /login, submit any email + password — bootstrap creates the user and signs you in.
# Build the CLI binary (needs bun, falls back to node+tsx in dev)
pnpm --filter @compass/cli build
node apps/cli/bin/compass.mjs login # device-code flow
node apps/cli/bin/compass.mjs capture "ship the auth changes"
node apps/cli/bin/compass.mjs statuspnpm --filter @compass/helper tauri dev
# After paired and approved via `compass login`-style device-code flow,
# Alt+Space pops the capture window from anywhere.apps/
web/ Next.js App Router web app — UI + API + webhooks + scheduler
cli/ compass CLI binary (capture / login / list / status)
helper/ Tauri menubar app (global hotkey + capture popover)
packages/
db/ Drizzle schema + queries + migrations (SQLite + Postgres)
shared/ types, ULID, time, Zod schemas, error classes
search/ SearchProvider + FTS5 (SQLite) + tsvector (Postgres) impls
notifications/ NotificationProvider + Telegram delivery, quiet-hours, drain
scheduler/ node-cron wrapper, daily digest + stall sweeper
api-client/ typed SDK shared by CLI / helper / future agents
infra/ Fly.io config, Litestream config, Dockerfile, start.sh
docs/ Implementation PRD + decision documents
design/ UI design spec (5 themes) + implementation reference
tests/ Vitest setup + Playwright E2E specs
| Command | What it does |
|---|---|
pnpm dev |
Run all apps in dev (parallel via Turbo) |
pnpm web |
Run web app only |
pnpm cli <args> |
Forward args to CLI dev mode |
pnpm helper |
Run Tauri helper in dev |
pnpm build |
Build all apps + packages |
pnpm typecheck |
TS typecheck across the workspace |
pnpm test |
Vitest (unit + integration) |
pnpm test:e2e |
Playwright smoke tests |
pnpm lint / pnpm lint:fix |
Biome lint |
pnpm db:generate |
Regenerate Drizzle migrations from schema |
pnpm db:migrate |
Apply migrations to the active dialect |
pnpm db:push:sqlite / pnpm db:push:pg |
Push schema directly (dev only) |
Compass runs against either SQLite (default, with Litestream continuous replication) or Postgres (e.g. Neon, Fly Postgres). Switch via COMPASS_DB_DIALECT=sqlite|pg. Per-dialect Drizzle schemas + migration sets are maintained; CI runs both paths.
# SQLite (default)
COMPASS_DB_DIALECT=sqlite COMPASS_SQLITE_PATH=./data/compass.db pnpm db:migrate
# Postgres
COMPASS_DB_DIALECT=pg DATABASE_URL=postgres://compass:compass@localhost:5432/compass pnpm db:migrateSearch abstraction sits behind @compass/search: FTS5 for SQLite, tsvector + GIN for Postgres. Single SearchProvider interface; pick at boot.
See .env.example for the full list. Required for first run:
| Variable | Required when | Notes |
|---|---|---|
COMPASS_DB_DIALECT |
always | sqlite (default) or pg |
COMPASS_SQLITE_PATH |
dialect=sqlite | e.g. ./data/compass.db |
DATABASE_URL |
dialect=pg | Postgres connection string |
COMPASS_SESSION_SECRET |
always | 32+ random chars |
COMPASS_WEBHOOK_HMAC_SECRET |
always | 32+ random chars |
COMPASS_TOKEN_PEPPER |
always | 16+ random chars — pepper for token hashing |
COMPASS_BASE_URL |
always | e.g. http://localhost:3000 |
COMPASS_TZ |
optional | default America/Chicago |
COMPASS_BOOTSTRAP_ALLOW_FIRST_USER |
first-time setup | 1 to allow the first signup form to create the user account |
TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID |
notifications | reuses your existing OpenClaw Telegram bot, or mint a new one |
LITESTREAM_REPLICA_URL + AWS keys |
SQLite in prod | continuous backup to R2/S3 |
- Three thin clients + one server. Web (browser + PWA on mobile) + CLI binary + native menubar helper. All hit a single deployed server with cookie session (browser) or bearer token (others).
- Auth principals: 3 token scopes + 1 cookie.
cli,helper,webhooktoken scopes. Webhook adds HMAC body signature on top of bearer. - Server-authoritative. No sync engines; clients have local outboxes that replay on reconnect. Idempotency keys per capture protect against double-fires.
- Polymorphic activity event log. Single
activity_eventtable powers the dashboard, momentum, stall detection, and Telegram digest.last_touched_atis denormalized convenience updated on any write (PRD §10.1). - 5 themes, runtime-switchable. White minimal (default) / dark minimal / outer space / white sand beach / dark forest. CSS-variable token system, glass material spec, Radix primitives + custom design.
fly launch --copy-config --config infra/fly.toml
fly volumes create compass_data --region ord --size 10
fly secrets set \
COMPASS_SESSION_SECRET=$(openssl rand -base64 32) \
COMPASS_WEBHOOK_HMAC_SECRET=$(openssl rand -base64 32) \
COMPASS_TOKEN_PEPPER=$(openssl rand -base64 24) \
TELEGRAM_BOT_TOKEN=... \
TELEGRAM_CHAT_ID=... \
LITESTREAM_REPLICA_URL=s3://your-bucket/compass \
LITESTREAM_ACCESS_KEY_ID=... \
LITESTREAM_SECRET_ACCESS_KEY=...
fly deployThe container restores from Litestream if the local volume is empty, applies migrations, then runs Litestream-wrapped Next.js (continuous backup while serving).
POST /webhooks/v1/runs/events
Authorization: Bearer <webhook-token>
X-Compass-Signature: sha256=<hex>
Content-Type: application/json
{
"run_id": "01HSC4Y3FAKERUNULIDXXXXXX",
"project_slug": "01HSC4Z0PROJECTULIDXXXXXX",
"event_seq": 3,
"event_type": "completed",
"occurred_at": "2026-05-23T04:17:00Z",
"payload": {
"result": "succeeded",
"body_markdown": "Tightened the auth middleware tests; added 4 new cases.",
"links": [{ "kind": "pr", "url": "https://...", "label": "PR #42" }],
"duration_ms": 1240000
}
}Idempotency on (run_id, event_seq). Duplicates return 409 { error.code: "duplicate" }. Full contract in PRD §7.
| Symptom | Cause / fix |
|---|---|
COMPASS_SESSION_SECRET must be at least 16 chars at boot |
set the env var (see Quickstart) |
First sign-in redirects to ?error=invalid and no user is created |
set COMPASS_BOOTSTRAP_ALLOW_FIRST_USER=1 (one-time, then remove) |
Telegram failed: 400: ... in notifications table |
bot token wrong, or message body contains MarkdownV2-special chars that need escaping |
| CLI says "outbox: 3 pending" but never sends | server unreachable; check compass status and COMPASS_BASE_URL |
| Webhook returns 403 with "invalid signature" | hex string + HMAC over the raw body, not re-encoded JSON |
| FTS5 search returns nothing | run pnpm db:migrate to ensure the search_index virtual table was created |
| Litestream restore on boot fails | check LITESTREAM_REPLICA_URL + AWS credentials; first deploy has no replica yet (this is normal) |
Personal project. Not redistributed.