Reports: the LLM reliably used company_enrichment for prose fields but inconsistently populated the parallel Finding-list/string-list fields from the same evidence, even with progressively more explicit prompting. Add a code-level backfill (products, recent developments, financial signals, strategic initiatives, regulatory signals, risks/opportunities mirrored from SWOT, unknowns, monitoring recommendations) that only ever fills in what the model left empty, never overwrites what it produced. Enrichment tab: reorder sections (Products/Recent updates before Customers/Competitors) and add a per-section "Refresh" button that re-fetches just one of NinjaPear's six independent per-company endpoints when it came back empty - confirmed live that a data-coverage gap (e.g. Amazon returning no products) is real provider behavior, not a bug. Auth: the first account registered on a deployment with zero existing admins is now auto-promoted to admin, closing the chicken-and-egg gap where the only path to admin access was direct DB access. Self-heals if the last admin ever deletes their account. Also bumps nginx's proxy_read_timeout for api.ciagent.org to cover the enrichment refresh's synchronous funding-endpoint call (up to 5 minutes per NinjaPear's docs). Co-Authored-By: Claude Sonnet 5 <[email protected]>
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, DEPLOYMENT.md for running this in production behind Cloudflare/Nginx, and docs/FIREBASE_MIGRATION.md for a (skeptical) look at what moving to Firebase would take.
A live reference deployment runs at ciagent.org, with its own code hosted on a self-hosted Gitea instance at git.ciagent.org (public read, admin-only write) — click "Git Repository" on the landing page.
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,enrichment
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
A fresh clone ships with zero API keys, on purpose. .env is gitignored and never committed; .env.example — the only thing that is committed — leaves every paid provider blank. There's no "secret" hidden somewhere in the repo history either; nothing paid has ever been checked in. Clone it, docker compose up, and the entire demo workflow (register/login, add a company, run a monitor, get a report, get an alert) works immediately against mock LLM/search providers and console-logged notifications — no signups, no billing, no keys, ever required just to try it.
Every paid/external provider defaults to a mock/console implementation this way. Automated tests always run against mocks too, and never call a paid API.
Adding real provider keys
Once you have your own API keys, there are two independent places to put them, and they serve different purposes:
-
.env(deployment-wide defaults) — see.env.examplefor the full list with comments. The key ones: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.NINJAPEAR_API_KEY/USPTO_API_KEY— optional company-enrichment and patent-search providers, both free-tier-friendly to start.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.
LLM_PROVIDER/SEARCH_PROVIDERare provider selection and only live here — after changing them, rebuild/restartapi,worker, andbeatto pick up the change. -
The app's own Settings page (no
.envedit, no restart) — once the app is running, sign in and go to Settings:- "Your API keys" — every account can set its own Anthropic/Brave/NinjaPear/USPTO key, used for that account's own companies. Falls back to the
.envvalue if you never set one. - "Server secrets" (admin accounts only) — Cloudflare Turnstile site key/secret and the Resend API key, shared server-wide, stored encrypted in the database, and take effect immediately for every visitor. This is the intended way to configure these three on a real deployment rather than editing
.envdirectly.
- "Your API keys" — every account can set its own Anthropic/Brave/NinjaPear/USPTO key, used for that account's own companies. Falls back to the
If LLM_PROVIDER/SEARCH_PROVIDER still show as mock in Settings → System configuration after you've set keys, that's the .env-level provider selection, not a missing key — it needs the .env edit + restart from option 1 above, not the Settings UI.
Known limitations
See KNOWN_LIMITATIONS.md.