Single-project Node/TypeScript app.
All user-owned code is under src/:
src/core- deterministic fetch/extract, LLM adapters, scoringsrc/api- Express APIsrc/web- React + Vite frontendsrc/cli- CLI command
5 stages:
URL → [1. Fetch, Extract & Parse] → [2. LLM Extraction] → [3. Deterministic Checks] → [4. LLM Assessment] → [5. Scoring]
pnpm install
cp .env.example .envRecommended .env:
PORT=3001
LLM_PROVIDER=anthropic
LLM_API_KEY=your_provider_api_key_here
LLM_MODEL=
LLM_TIMEOUT_MS=120000
MAX_ARTICLE_CHARS=20000
CONTEXT_FILE_PATH=context.txt
VITE_API_URL=/api/analyse
VITE_API_START_URL=/api/analyse/start
VITE_API_STATUS_URL_BASE=/api/analyse/statusLLM_MODEL is optional. If empty, provider defaults are used:
- Anthropic:
claude-haiku-4-5 - OpenAI:
gpt-5-mini-2025-08-07 - Gemini:
gemini-3-flash-lite-preview - Grok:
grok-4-1-fast-non-reasoning-latest
Performance tuning:
LLM_TIMEOUT_MS: timeout for provider requests (default90000)MAX_ARTICLE_CHARS: max article chars included in the LLM prompt (default12000)CONTEXT_FILE_PATH: where clean extracted article text is saved (defaultcontext.txt)
Run both the API and front end in separate terminals. I recommend using the front end, but it is possible to use the CLI (although not as nice)
API:
pnpm dev:apiWeb:
pnpm dev:webThe web UI shows a single live STATUS: ... line that updates until completion.
Each completed run overwrites context.txt with clean extracted article text.
- API:
http://localhost:3001 - Web:
http://localhost:5173
Health check:
curl http://localhost:3001/healthGET /healthPOST /analysePOST /analyse/start(starts async analysis and returnsrequestId)GET /analyse/status/:requestId(returns latest status, result, or error)
Request body:
{
"name": "John Smith",
"dob": "1980-03-15",
"url": "https://example.com/article"
}Run:
pnpm analyseInteractive behavior:
- CLI first checks
.envforLLM_PROVIDER,LLM_MODEL, andLLM_API_KEY. - If any of those are missing (or invalid provider), it prompts for them one-by-one.
- Once LLM settings are resolved, it prompts one-by-one for:
namedob(YYYY-MM-DD)url(http(s)://...)
You can still pass values as flags to skip prompts for those fields:
pnpm analyse --name "John Smith" --dob "1980-03-15" --url "https://..." --provider grok --api-key "xai-..." --model "grok-3-fast-latest"Behavior:
- Calls core engine directly
- Prints live
STATUS: ...progress lines to stderr while running - Prints JSON to stdout
- Errors go to stderr and exit code
1
pnpm typecheck
pnpm build