@AGENTS.md # Marketing Command Center Next.js 16 app that uses Claude Code CLI as a subprocess to run an 8-agent marketing pipeline. Generates ads, videos, copy, and research for mobile apps. ## Tech Stack - Next.js 16.2.1, React 19, TypeScript 5, TailwindCSS 4, shadcn/ui - Prisma 7.5 with SQLite (better-sqlite3 adapter) - NextAuth 5 (credentials provider, `trustHost: true` required) - Claude Code CLI spawned via `child_process.spawn()` (NOT the Anthropic SDK) - Playwright for HTML-to-PNG rendering - Remotion for video generation (in `pipeline/remotion-ad/`) ## How Claude Is Used The app spawns `claude -p --output-format stream-json` as a subprocess. Auth comes from the user's Claude Max subscription. In Docker, auth is via `CLAUDE_CODE_OAUTH_TOKEN` env var (an access token extracted from macOS Keychain `Claude Code-credentials` entry). See `lib/claude.ts` for all spawn logic. ## Local Development ```bash npm install npx prisma db push npx prisma db seed # creates admin user + honeyDue app npm run dev # http://localhost:3000 ``` Default login: `admin@localhost` / `admin123` (set via ADMIN_EMAIL/ADMIN_PASSWORD env vars). ## Unraid Docker Deployment The app runs on an Unraid server at `marketing.88oakapps.com` behind Nginx Proxy Manager. ### Architecture - App source: `/mnt/user/appdata/marketing/` (disposable, rsync from dev machine) - Persistent data: `/mnt/user/downloads/marketing/` (survives app updates) - `db/` - SQLite database - `outputs/` - generated campaign assets (ads, videos, copy) - `knowledge/` - brand identity, platform guidelines ### Unraid docker-compose.yml (lives on Unraid, NOT the repo version) The repo `docker-compose.yml` includes Postiz services for local dev. The Unraid version is app-only: ```yaml services: app: build: . ports: - "3000:3000" environment: - NEXTAUTH_URL=http://localhost:3000 - NEXTAUTH_SECRET=${NEXTAUTH_SECRET} - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@localhost} - ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123} - DATABASE_URL=file:./prisma/data/marketing.db - PIPELINE_ROOT=/app/pipeline - CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN} volumes: - /mnt/user/downloads/marketing/db:/app/prisma/data - /mnt/user/downloads/marketing/outputs:/app/pipeline/outputs - /mnt/user/downloads/marketing/knowledge:/app/pipeline/knowledge ``` ### Deploying Updates to Unraid ```bash # Sync code (excludes env/compose so Unraid config isn't overwritten) rsync -avz \ --exclude='node_modules' --exclude='.next' --exclude='prisma/data' \ --exclude='pipeline/outputs' --exclude='pipeline/remotion-ad/.next' \ --exclude='pipeline/remotion-ad/node_modules' \ --exclude='.env' --exclude='.env.local' --exclude='docker-compose.yml' \ --delete \ ./ unraid:/mnt/user/appdata/marketing/ # Rebuild and restart ssh unraid "cd /mnt/user/appdata/marketing && docker compose down && docker compose build app && docker compose up -d" ``` ### Claude Auth in Docker Claude Max uses OAuth — tokens stored in macOS Keychain locally, but the headless Docker container has no keychain, so it needs the OAuth access token explicitly. The token is rotated from the **Settings page** (`/settings` → Claude card); the DB value is injected into the spawned `claude` subprocess env at launch time and overrides `CLAUDE_CODE_OAUTH_TOKEN` from the container env. The Test button on the same card validates the token by hitting the Anthropic messages API and surfaces 401s as "Token expired or invalid". To mint a token: 1. Run `claude setup-token` locally, open the magic link in browser 2. Extract the access token: `security find-generic-password -s "Claude Code-credentials" -a "$(whoami)" -w | python3 -c 'import sys,json; print(json.load(sys.stdin)["claudeAiOauth"]["accessToken"])'` 3. Paste it into Settings → Claude → Save (no rebuild required) `CLAUDE_CODE_OAUTH_TOKEN` in `.env` still works as a bootstrap fallback (used on first boot before any DB value is saved, or if the DB is wiped). There is no Anthropic UI to list or revoke `setup-token` outputs — they live ~1 year. Treat each one like a password. ### Volume Permissions Host directories must be owned by UID 1000 (node user in container): ```bash ssh unraid "chown -R 1000:1000 /mnt/user/downloads/marketing/{db,outputs,knowledge}" ``` ### Dockerfile Notes - Base image: `node:20-slim` (NOT alpine — Playwright needs apt/Debian) - Startup script (`start.sh`) runs `prisma db push` and `prisma db seed` before `node server.js` - Seed is idempotent (uses upsert), safe to run on every restart ## Key Files - `lib/claude.ts` - Claude CLI spawn logic, pipeline orchestration, chat sessions - `lib/auth.ts` - NextAuth config (trustHost: true for reverse proxy) - `lib/scanner.ts` - scans pipeline output directories for assets - `prisma/schema.prisma` - 8 models: User, App, Campaign, AgentRun, Asset, ClaudeSession, TrendReport, Setting - `prisma/seed.ts` - creates admin user + honeyDue app - `pipeline/CLAUDE.md` - agent system docs (read this for pipeline details) - `pipeline/skills/` - 8 agent skill definitions - `pipeline/knowledge/` - brand assets & guidelines ## SSH Unraid is accessible via `ssh unraid` (configured in ~/.ssh/config on dev machine).