Skip to content

Repository files navigation

Somnus

A society of the well-rested. Entry is by mark only.

Members join through another member's link, take a pseudonym, and connect the wearable they sleep with (WHOOP or Oura). There is no account: the wearable is the login. Every morning a Telegram bot reads the house — who testified, who cleared the bar, who was silent — under pseudonyms only. The rules live in the Charter.

This is v0: the ledger and the reading. The charter's exile rule is computed and shown, not yet enforced.

How it fits together

/j/<slug>          the invitation: "You have been marked by Veiled Heron."   [public]
/enter             roll a name, pick a device -> OAuth                        [public]
/api/connect/*     OAuth start + callback; creates the member, sets a signed cookie
/me                your nights, streak, standing, your own mark, "Receive your summons"
/ledger            the week, for those within
/charter           the rules, live numbers

/api/cron/digest   sync everyone, then post the morning reading to Telegram    [cron, 06:00 UTC]
/api/cron/sync     sync everyone (Pro plans; Hobby crons run once a day)       [cron]
/api/telegram      bot webhook: /start <token> links a member, hands out a one-time group invite; /ladder
  • Next.js 16 (App Router) on Vercel, Supabase Postgres accessed only from the server with the service role. No client-side database access, so tables carry RLS with no policies.
  • Sessions are HMAC-signed cookies holding the member id. Reconnecting an already-known wearable logs you back in instead of creating a duplicate.
  • Scores are provider-native. WHOOP sleep_performance_percentage; Oura daily_sleep.score. The bar is per device (src/lib/rules.ts). Ranking is by nights cleared, then streak — never by raw score across devices.
  • Polling, not webhooks. Each sync re-pulls the trailing three nights, which also picks up score revisions.
  • All rules are pure functions in src/lib/rules.ts with tests.

Setup

1. Supabase

Create a project. Run supabase/migrations/0001_init.sql in the SQL editor. Copy the project URL and the service role key.

2. WHOOP

Create an app at https://developer.whoop.com. Redirect URI: https://<your-domain>/api/connect/whoop/callback. Scopes: read:sleep read:profile offline.

3. Oura

Create an app at https://cloud.ouraring.com/oauth/applications. Redirect URI: https://<your-domain>/api/connect/oura/callback. Oura returns 403 for members whose Oura subscription has lapsed; the connection is marked lapsed rather than treated as a bad night.

4. Telegram

  1. Create a bot with @BotFather. Note the token and username.

  2. Create the group. Add the bot as an admin with permission to invite users via link.

  3. Get the group's chat id (a negative number). Easiest: temporarily add @userinfobot, or call https://api.telegram.org/bot<token>/getUpdates after posting in the group.

  4. After deploying, register the webhook once:

    curl -X POST -H "Authorization: Bearer $CRON_SECRET" https://<your-domain>/api/telegram/setup

5. Deploy

cp .env.example .env.local   # fill in
pnpm install
pnpm dev

On Vercel, set every variable from .env.example. vercel.json schedules the digest at 06:00 UTC (09:00 Helsinki) and a sync every three hours; on the Hobby plan only the daily digest will run, and members can use "Sync now" on their page. Adjust the cron hour for your group's timezone.

To post the reading by hand:

curl -H "Authorization: Bearer $CRON_SECRET" https://<your-domain>/api/cron/digest

The first member

The first person has no sponsor. Open /enter directly (no ?via=), connect a device, and share the mark from /me. Everyone after enters through someone.

Development

pnpm typecheck   # next typegen + tsc
pnpm test        # vitest (rules, date handling)
pnpm lint

Not yet

  • Apple Health / Garmin / Fitbit (an aggregator or bridge app; Apple Health has no cloud API)
  • Enforcement of exile and the rite of return
  • Token encryption at rest (service-role-only today; encrypt before the group is strangers)
  • Webhooks from WHOOP and Oura (polling is enough at this size)
  • An in-house chat, when pseudonymity needs to hold beyond people you already know

Licence

MIT

About

A society of the well-rested. Entry is by mark only.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages