App Developer Docs
Setup & Running
The app is split into two processes: a Hono backend (port 3001) and a Vite dev server for the Vue frontend (port 5173). In development you run both; in production the backend serves the built frontend as static files.
Prerequisites
- Node.js 20+
- An OpenAI API key
- (Optional) SMTP credentials for real magic-link emails — in development, links are printed to the terminal
First-time setup
# 1 — Install backend dependencies
cd backend
npm install
# 2 — Configure environment
cp .env.example .env
# Open .env and at minimum set:
# OPENAI_API_KEY=sk-...
# OPENAI_MODEL=gpt-5-mini (or whatever current model you want)
# 3 — Install frontend dependencies
cd ../frontend
npm install
Running in development
From the repo root:
./dev.sh
This starts both processes. Frontend hot-reloads are proxied transparently through Vite to the backend on port 3001.
Or run them separately in two terminals:
# Terminal 1
cd backend && npm run dev
# Terminal 2
cd frontend && npm run dev
Open http://localhost:5173.
Magic links in development
Real SMTP is not required locally. When NODE_ENV is not production, the backend logs the magic link URL directly to the terminal instead of sending an email:
[dev] Magic link for you@email.com :
http://localhost:3001/api/auth/magic?token=abc123...
Click that URL in your terminal to complete sign-in.
Running the test suites
# Backend integration tests
cd backend && npm test
# Frontend component tests
cd frontend && npm test
Tests use an in-memory SQLite database and mock both email sending and OpenAI calls — no environment variables required.
Production build
cd frontend && npm run build # outputs to frontend/dist/
cd ../backend && NODE_ENV=production npm start
The backend serves frontend/dist/ as static files and handles all /api/* routes. A single process on a single port.
Deployment targets
Production runs on Hostinger and uses the root sulcideploy.sh helper.
Fly.io is still supported from both the main local clone and the remotedev clone:
# Fly app from fly.toml
flyctl deploy
# Fly dev app from fly.dev.toml
flyctl deploy -c fly.dev.toml
The Fly Docker build is intentionally separate from the Hostinger install flow. Hostinger uses the root postinstall and committed build artifacts; Fly builds backend dependencies, the Vue frontend, and the Astro site inside Docker.
Environment variables
All variables live in backend/.env. See backend/.env.example for the full list.
| Variable | Required | Default | Notes |
|---|---|---|---|
PORT | no | 3001 | Backend listen port |
DB_PATH | no | ./sulci.db | SQLite file path. Use :memory: for tests. |
OPENAI_API_KEY | yes | — | Your OpenAI secret key |
OPENAI_MODEL | no | gpt-5-mini | Model sent to the completions API |
APP_URL | no | http://localhost:3001 | Base URL for magic-link emails. Must be the public URL in prod (https://downshiftr.com) or links point at localhost. |
COOKIE_SECURE | no | false | Set to true in production (HTTPS only) |
MAIL_HOST / MAIL_PORT / MAIL_USER / MAIL_PASS | no | — | SMTP for real email. Read first; falls back to SMTP_HOST/SMTP_PORT/SMTP_USER/SMTP_PASS. |
SMTP_FROM / SMTP_FROM_NAME | no | — | Sender address and display name |
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY | no | — | Web Push keys. Generate once: node -e "console.log(require('web-push').generateVAPIDKeys())". Blank = push disabled (subscribe/test/tick no-op). |
VAPID_SUBJECT | no | mailto:admin@downshiftr.com | Contact URI sent to push services |
CRON_SECRET | no | — | Shared secret for the notification cron. The cron calls GET /api/push/tick with header x-cron-key: $CRON_SECRET. |
Prod env on Hostinger: values are injected at git-deploy time from
.builds/config/.env(set via the hPanel Node env UI). Editing that file + restarting does not update the live process — push a deploy to apply env changes.