Setup
From a fresh clone to every app running locally.
You need node 24, pnpm, bun and docker.
pnpm install
cp .env.example .env
pnpm db:up
pnpm dev --filter='!extension'Edit .env before the first start. BETTER_AUTH_SECRET must be at least 32 characters (openssl rand -base64 32 gives 44), otherwise the api refuses to start. ADMIN_PASSWORD must be at least 10 characters, and with ADMIN_EMAIL or ADMIN_PASSWORD empty no admin is created. DATABASE_URL in .env.example already matches the local postgres from pnpm db:up.
pnpm dev runs turbo dev, which starts every workspace with a dev script: web, api, dashboard, docs and the extension (wxt, which opens a development browser). pnpm dev --filter='!extension' starts only the four apps. Run the extension on its own with pnpm --filter extension dev.
pnpm db:up starts a local postgres (pgvector/pgvector:pg17) from docker/docker-compose.yml. The api applies migrations on start and, when SEED=demo, fills an empty database with sample data.
The pgvector image is debian based, the old alpine based postgres image used a different libc, so the compose file mounts a new volume, skiffr-pgvector-data, and never reuses the old one. Never point the new image at an old alpine data directory, text indexes break silently. Remove the old dev volume (it holds the old data) with docker volume rm docker_skiffr-pg.
| App | Url |
|---|---|
| web | http://localhost:3000 |
| api | http://localhost:3001 |
| dashboard | http://localhost:3002 |
| docs | http://localhost:3003 |
Sign in to the dashboard with ADMIN_EMAIL and ADMIN_PASSWORD from .env. The admin is created or reset on every api start.
Load the extension
Build it, load it unpacked, create a token on the dashboard's Extension page and paste it in the extension options. The steps, the CI build option and what the extension does on a page are in the extension.
Working on one app
pnpm dev:web # web and api
pnpm dev:dashboard # dashboard and apiBefore you push
pnpm lint
pnpm check-types
pnpm test
pnpm build