- TypeScript 86.8%
- Nunjucks 12.8%
- CSS 0.1%
- Dockerfile 0.1%
|
All checks were successful
Build & Push Docker Image / build (push) Successful in 51s
Each card now ships memberInterest with per-member score + rated sources; multi-member audiences render tier-ringed avatars with hover tooltips and a "View by member" sidebar that reorders cards client-side. Adds ?debug=recs for inspecting raw source contributions. Fixes buildConsensusPicks to enforce all-members-influence (was missing the COUNT(DISTINCT) HAVING) and adds audience.last_refreshed_at so the TMDB worker re-scores after new ratings even when source rec_links are already cached. Bumps the Ollama timeout default from 60s to 180s. Reviewed-on: #19 |
||
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| db | ||
| dev | ||
| docs | ||
| example | ||
| scripts | ||
| src | ||
| tests | ||
| .env.example | ||
| .gitignore | ||
| .npmrc | ||
| .nvmrc | ||
| AGENTS.md | ||
| biome.json | ||
| CLAUDE.md | ||
| Dockerfile | ||
| Dockerfile.ci | ||
| knexfile.ts | ||
| lefthook.yml | ||
| LICENSE | ||
| package-lock.json | ||
| package.json | ||
| playwright.config.ts | ||
| playwright.parallel.config.ts | ||
| README.md | ||
| tailwind.config.js | ||
| tsconfig.json | ||
| vitest.config.ts | ||
| vitest.live.config.ts | ||
watchcraft
Self-hosted media recommendation app for Jellyfin libraries. Rate titles from your library, discover similar content via TMDB, and optionally request media through Jellyseerr or get one-line AI explanations via Ollama.
Features
- Star-rating UI for your Jellyfin library
- Discovery feed powered by TMDB recommendations
- Request buttons via Seerr / Jellyseerr (optional)
- One-sentence AI explanations via Ollama (optional)
- Multi-user support with shared audiences
- Three auth modes: local accounts, SSO proxy header, or single implicit user
- SQLite (zero-config), PostgreSQL, or MariaDB
Requirements
- A running Jellyfin instance
- A free TMDB API key
- Docker
Quick start
cp docker-compose.yml docker-compose.local.yml # or edit in place
# Fill in JELLYFIN_URL, JELLYFIN_API_KEY, TMDB_API_KEY, SESSION_SECRET
docker compose up -d
# Open http://localhost:3000 — setup wizard runs on first visit (local auth)
Generate a session secret:
openssl rand -hex 32
Configuration
Required
| Variable | Description |
|---|---|
JELLYFIN_URL |
Base URL of your Jellyfin instance (e.g. http://jellyfin:8096) |
JELLYFIN_API_KEY |
Jellyfin API key |
TMDB_API_KEY |
TMDB v3 API key (free tier is sufficient) |
SESSION_SECRET |
Random string for signing session cookies — use a long random value in production |
Auth
| Variable | Default | Description |
|---|---|---|
AUTH_MODE |
local |
local | proxy | none |
AUTH_HEADER |
X-authentik-username |
Header to read username from when AUTH_MODE=proxy |
Auth modes:
local— bcrypt password auth with Express sessions. A setup wizard runs on first visit; the first account created is always admin.proxy— reads the username fromAUTH_HEADER. Use with Authentik, Authelia, or any other SSO proxy. SetAUTH_MODE=proxyandAUTH_HEADERto match your proxy's header name.none— single implicit user nameddefault. For trusted-network-only deployments with no login UI.
Database
| Variable | Default | Description |
|---|---|---|
DB_CLIENT |
better-sqlite3 |
better-sqlite3 | pg | mysql2 |
DB_SQLITE_PATH |
./data/db.sqlite |
SQLite file path (SQLite only) |
DB_HOST |
— | Hostname (PostgreSQL / MariaDB) |
DB_PORT |
— | Port (PostgreSQL / MariaDB) |
DB_NAME |
— | Database name |
DB_USER |
— | Database user |
DB_PASSWORD |
— | Database password |
SQLite is zero-config and the default. PostgreSQL and MariaDB are useful if you want to run multiple watchcraft users off a shared database instance.
Optional integrations
| Variable | Description |
|---|---|
SEERR_URL |
Seerr / Jellyseerr base URL — enables Request buttons on discovery cards |
SEERR_API_KEY |
Seerr API key |
OLLAMA_URL |
Ollama base URL — enables one-sentence recommendation explanations |
OLLAMA_MODEL |
Ollama model to use (default: llama3.2:3b) |
Cache TTLs
All intervals have sensible defaults. Override if needed.
| Variable | Default | Description |
|---|---|---|
CACHE_LIBRARY_HOURS |
6 |
How often to re-sync the Jellyfin library |
CACHE_HISTORY_HOURS |
1 |
How often to re-sync play history |
CACHE_TMDB_DAYS |
30 |
How long TMDB recommendation data is cached |
CACHE_METADATA_DAYS |
90 |
How long TMDB metadata is cached |
CACHE_SEERR_MINUTES |
15 |
How often to poll Seerr for request status |
CACHE_MAX_REC_LINKS |
50000 |
Max recommendation graph edges before cache pruning |
Misc
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port |
TZ |
Etc/UTC |
Timezone (used for cron scheduling) |
SESSION_STORE_PATH |
./data/sessions.sqlite |
Path for the session SQLite store |
LOG_LEVEL |
info |
trace | debug | info | warn | error | fatal |
LOG_FORMAT |
pretty |
pretty (human-readable) | json (for log aggregators) |
Data & persistence
The app writes two files to /app/data:
db.sqlite— application databasesessions.sqlite— session store
Mount /app/data to a persistent volume so data survives container restarts. The example docker-compose.yml uses a named Docker volume (watchcraft-data).
Database migrations run automatically on every startup — no manual migration step needed.
Upgrading
docker compose pull
docker compose up -d
Migrations run on startup; no other steps required.
Health check
GET /health returns 200 OK when the app is ready.
Development
Requirements
Setup
nvm use # or: fnm use
npm install
cp .env.example .env
# Fill in TMDB_API_KEY (required), others optional for dev
npm run dev:up # start Docker dev services
npm run dev:bootstrap # configure Jellyfin, write .env.dev
npm run dev # start watchcraft in watch mode (uses .env.dev)
Tests
npm test # unit + integration (SQLite)
npm run test:all-dbs # unit + integration (SQLite + Postgres + MariaDB)
Browser tests (Playwright)
One-time host setup (requires system package manager access for Chromium and Firefox):
npx playwright install chromium
npx playwright install-deps chromium
npx playwright install webkit
npx playwright install firefox
npx playwright install-deps firefox # needs sudo on Linux/WSL
WebKit ships its own dependencies — no install-deps call needed for webkit.
npm run test:browser # all 9 device tiers (~18 min)
npm run test:browser:desktop # Chromium: Desktop Chrome
npm run test:browser:desktop-safari # WebKit: Desktop Safari
npm run test:browser:tablet # WebKit: iPad Mini
npm run test:browser:mobile # WebKit: iPhone 14
npm run test:browser:android-phone # Chromium: Pixel 7
npm run test:browser:android-tablet # Chromium: Galaxy Tab S9
npm run test:browser:firefox-desktop # Firefox: desktop
npm run test:browser:firefox-tablet # Firefox: tablet viewport
npm run test:browser:firefox-phone # Firefox: phone viewport
npm run test:browser:update # refresh visual-regression baselines
Each per-device command is self-contained: it resets the dev stack, starts the server, authenticates, runs tests, and stops the server (~2 min per tier).