sakshamandClaude Sonnet 5 18305b545c Backfill sparse report sections, add enrichment section refresh, and bootstrap first-admin
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]>
2026-08-06 17:41:02 -04:00

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:

  1. .env (deployment-wide defaults) — see .env.example for 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 — set ANTHROPIC_API_KEY/ANTHROPIC_MODEL, OLLAMA_BASE_URL/OLLAMA_MODEL, or GEMINI_API_KEY/GEMINI_MODEL to 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 — set BRAVE_SEARCH_API_KEY to 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 set RESEND_API_KEY to actually test email delivery (alerts and security email both use this).
    • NOTIFICATION_SMS_ENABLED=false by default — set to true and provide TWILIO_* to enable SMS.

    LLM_PROVIDER/SEARCH_PROVIDER are provider selection and only live here — after changing them, rebuild/restart api, worker, and beat to pick up the change.

  2. The app's own Settings page (no .env edit, 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 .env value 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 .env directly.

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.

S
Description
CI Agent - competitive intelligence monitoring
Readme
924 KiB
Languages
Python 72.3%
TypeScript 27.1%
Shell 0.4%