FastAPI + Celery + Next.js + Postgres/Redis app with company monitoring, source collection, LLM-based change analysis, enrichment, and account security (Turnstile, escalating lockout, email verification).
CI Agent — Competitive Intelligence Monitoring
CI Agent monitors companies you choose, collects publicly available information from multiple sources on a schedule, analyzes it with an LLM into an evidence-linked report, detects meaningful changes between runs, and alerts you by email (and optionally SMS) with a severity and confidence score.
See PLAN.md for the build strategy, ARCHITECTURE.md for system design, SECURITY.md for the threat model, TASKS.md for the live implementation checklist, KNOWN_LIMITATIONS.md for what's stubbed vs. fully live, and docs/FIREBASE_MIGRATION.md for a (skeptical) look at what moving to Firebase would take.
Quick start (Docker)
git clone <repository>
cd ci-agent
cp .env.example .env
docker compose up --build
Then visit:
| Service | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend API | http://localhost:8000 |
| API docs (Swagger) | http://localhost:8000/docs |
The default .env.example runs in AUTH_MODE=local (no login required, fixed dev user) with LLM_PROVIDER=mock and SEARCH_PROVIDER=mock — the whole demo workflow works with zero paid API keys.
api, worker, and beat build from the same Dockerfile but are separate images — after adding a Python dependency, run docker compose build api worker beat (not just restart) or they'll crash with ModuleNotFoundError on stale images.
Similarly, after adding an npm dependency to apps/web, a plain rebuild isn't enough either — web's node_modules and .next are both persistent anonymous Docker volumes that survive docker compose build web and even a plain docker compose restart web. This means a new package can still 404, and — on Windows + Docker Desktop — an edited existing file can keep serving its pre-edit output after a restart, since the on-disk .next build cache isn't cleared by a restart. Run docker compose rm -f -s -v web && docker compose up -d web to actually pick up new packages or force a clean recompile.
Running without Docker
Backend:
cd apps/api
python -m venv .venv
./.venv/Scripts/activate # or `source .venv/bin/activate` on macOS/Linux
pip install -e ".[dev]"
cp ../../.env.example ../../.env # edit DATABASE_URL to the sqlite line if you don't have Postgres running
alembic upgrade head
uvicorn app.main:app --reload
Frontend:
cd apps/web
npm install
npm run dev
Common commands
# Database migrations
cd apps/api && alembic upgrade head
cd apps/api && alembic revision --autogenerate -m "description"
# Run a Celery worker + beat (only needed outside Docker)
cd apps/api && celery -A app.tasks.celery_app worker --loglevel=INFO -Q default,collection,analysis,notifications,maintenance
cd apps/api && celery -A app.tasks.celery_app beat --loglevel=INFO
# Backend tests / lint / format
cd apps/api && pytest
cd apps/api && ruff check app tests
cd apps/api && black app tests
# Frontend tests / lint / format / typecheck
cd apps/web && npm test
cd apps/web && npm run lint
cd apps/web && npm run format
cd apps/web && npm run typecheck
# End-to-end test (requires `docker compose up -d` already running)
cd apps/web && npx playwright install --with-deps chromium # one-time
cd apps/web && npm run e2e
Configuration
All configuration is via environment variables — see .env.example for the full list with comments. Highlights:
AUTH_MODE=local|jwt— local single-user dev mode vs. real email/password accounts.LLM_PROVIDER=mock|anthropic|ollama|gemini— setANTHROPIC_API_KEY/ANTHROPIC_MODEL,OLLAMA_BASE_URL/OLLAMA_MODEL, orGEMINI_API_KEY/GEMINI_MODELto go live. Gemini has a genuine free tier — grab a key at aistudio.google.com/apikey — so it's the cheapest provider to actually try against a real model.SEARCH_PROVIDER=mock|brave— setBRAVE_SEARCH_API_KEYto go live.SMTP_HOST/RESEND_API_KEY— no bundled local mail sink; point SMTP at a real relay or setRESEND_API_KEYto actually test email delivery (alerts and security email both use this).NOTIFICATION_SMS_ENABLED=falseby default — set totrueand provideTWILIO_*to enable SMS.
Every paid/external provider defaults to a mock/console implementation. Automated tests always run against mocks and never call a paid API.
Known limitations
See KNOWN_LIMITATIONS.md.