| assets/css | ||
| components | ||
| composables | ||
| layouts | ||
| middleware | ||
| pages | ||
| public | ||
| server | ||
| tests | ||
| types | ||
| utils | ||
| .dockerignore | ||
| .gitignore | ||
| .prettierignore | ||
| .prettierrc.json | ||
| AGENTS.md | ||
| app.config.ts | ||
| app.vue | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| drizzle.config.ts | ||
| nuxt.config.ts | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Campfire Core
Campfire Core is a Nuxt 4 application and server API for a personal reading dashboard. It stores ingested items in PostgreSQL with pgvector, ranks them into a three-part dashboard, and uses local or hosted language models for embeddings and news-event processing.
Project outline
pages/contains the dashboard, people, setup, recently-read, and collection views. Collections include unsorted items, promoted evergreen collections, and model-suggested clusters.components/contains the dashboard item, connection, modal, navigation, login, message, HTML-preview, and loading UI.server/api/contains the authenticated browser API, setup endpoints, and internal maintenance endpoints for embedding, interests, events, and news.server/domain/contains ranking, matching, affinity, interest, link, and news-evidence logic that is mostly covered bytests/.server/utils/contains database access, content extraction, embeddings, news jobs, collection ordering, identity handling, and reinforcement helpers.server/db/contains the Drizzle schema and checked-in PostgreSQL migrations.
The production container builds the Nuxt server, runs pending migrations at
startup, and listens on port 3000.
Development
npm ci
npm run dev
The application expects PostgreSQL with the vector extension (for example,
the pgvector/pgvector image). The default connection is
postgres://n8n:changeme@postgres:5432/n8n; set
NUXT_PG_CONNECTION_STRING for another database.
Runtime configuration
All settings are read from Nuxt runtime configuration. The important variables are:
| Variable | Purpose | Default |
|---|---|---|
NUXT_PG_CONNECTION_STRING |
PostgreSQL connection string | postgres://n8n:changeme@postgres:5432/n8n |
NUXT_OPENWEBUI_URL |
Ollama-compatible Open WebUI endpoint | http://openwebui:8080 |
NUXT_OPENWEBUI_API_KEY |
Optional Open WebUI bearer token | empty |
NUXT_OPENROUTER_URL |
OpenRouter API endpoint | https://openrouter.ai/api/v1 |
NUXT_OPENROUTER_API_KEY |
Optional hosted news-model token | empty |
NUXT_EMBED_MODEL |
Embedding model | qwen3-embedding:0.6b |
NUXT_EMBED_DIMENSIONS |
pgvector embedding dimensions | 768 |
NUXT_EMBED_BATCH_SIZE |
Embeddings per request | 32 |
NUXT_EMBED_MAX_CHARACTERS |
Maximum embedding chunk size | 8000 |
NUXT_EMBED_NUM_CTX |
Ollama embedding context size | 32768 |
NUXT_NEWS_MODEL |
News classification model | typesafe/jev-1.13 |
NUXT_NEWS_SUMMARY_MODEL |
News summary model | gemma4:e4b |
NUXT_NEWS_BATCH_SIZE |
Items processed per news batch | 20 |
NUXT_NEWS_MAX_CANDIDATES |
Candidate events per item | 12 |
NUXT_NEWS_MAX_CHARACTERS |
News evidence text limit | 6000 |
NUXT_NEWS_CONVERSATION_CONFIDENCE |
Conversation-event confidence threshold | 0.9 |
NUXT_NEWS_MIN_MEMBERS |
Minimum event members | 2 |
NUXT_SETUP_TOKEN |
Optional token protecting first-user setup | empty |
NUXT_N8N_ENGAGEMENT_WEBHOOK_URL |
Optional engagement/collection webhook | empty |
Embedding operations use Open WebUI. News classification can use OpenRouter when its API key is configured and otherwise uses the Open WebUI Ollama endpoint.
Run the checked-in migrations with:
npm run db:migrate
API and maintenance jobs
Browser-facing routes are under /api/v1: the dashboard feed (fire),
collections and recommendations, item content and connections, people, unread
state, recently-read items, evergreen saves, and engagement. /api/auth holds
the setup and authentication checks. /api/v1/internal contains authenticated
automation and maintenance endpoints and should not be exposed publicly.
The news endpoints are designed for scheduled n8n calls:
POST /api/v1/internal/news/processstarts classification and returns ajobId; poll its status endpoint.POST /api/v1/internal/news/reconcilemerges or splits event records; poll its status endpoint.POST /api/v1/internal/news/expireremoves expired events.GET /api/v1/internal/news/visibilitydiagnoses event visibility gates.
Embedding backfills, interest synchronization, event insertion/regeneration,
Semble confirmations, and unsorted-content backfills are also available under
the internal namespace. Engagement synchronization reconciles self-authored
items and updates interest and affinity models. Daily interest synchronization
also expires old dashboard items. Collection recommendation maintenance refreshes
suggested clusters and cached collection links. These jobs keep maintenance out
of client-facing requests. Inspect server/api/v1/internal/ for the exact
request shapes.
Verification
npm run lint
npm test
npm run typecheck
npm run build
npx drizzle-kit check
Container
The Dockerfile builds a production Node server, runs database migrations on startup, and exposes port 3000. A Compose service can use this repository directly as its build context:
services:
campfire:
build: https://code.incremental.social/thepaperpilot/campfire-core.git
ports:
- '3000:3000'
environment:
NUXT_PG_CONNECTION_STRING: postgres://n8n:changeme@postgres:5432/n8n
NUXT_OPENWEBUI_URL: http://openwebui:8080
NUXT_OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-}
NUXT_EMBED_MODEL: qwen3-embedding:0.6b
NUXT_EMBED_DIMENSIONS: '768'
NUXT_EMBED_BATCH_SIZE: '32'
NUXT_EMBED_MAX_CHARACTERS: '8000'
NUXT_EMBED_NUM_CTX: '32768'
NUXT_NEWS_MODEL: typesafe/jev-1.13
NUXT_NEWS_SUMMARY_MODEL: gemma4:e4b
NUXT_NEWS_CONVERSATION_CONFIDENCE: '0.9'
NUXT_NEWS_MIN_MEMBERS: '2'
NUXT_SETUP_TOKEN: ${CAMPFIRE_SETUP_TOKEN:-}
Provide the runtime variables through Compose. The container waits for the database connection and runs pending migrations before serving traffic.
Automated News Events
The n8n event workflow should periodically call the authenticated
POST /api/v1/internal/news/process endpoint. Campfire scans recent unassigned
items, asks the configured Ollama model whether each belongs to an active event,
and creates or updates the event automatically. Processing runs as a
background job, so the POST responds immediately with a jobId:
POST /api/v1/internal/news/process (empty body)
GET /api/v1/internal/news/process/status?jobId=...
Poll the status endpoint until status is completed (the full result is in
result) or failed (details in error). Pass
{"lookbackMinutes": 4320} in the body only when recovering a backlog; the
default 30-minute window is right for steady state. The lower-level
POST /api/v1/internal/events endpoint is also available when n8n has already
performed classification.
{
"title": "Example event",
"summary": "What happened and why it matters.",
"members": [
{
"itemId": "00000000-0000-0000-0000-000000000000",
"confidence": 0.94,
"relationship": "report"
}
],
"model": "llama3.2:3b",
"modelVersion": "news-v1"
}
Events appear as priority items in Hearth. Their members are read individually, including messages selected from otherwise unrelated source conversations.
Run reconciliation less frequently, such as every 3-4 hours:
POST /api/v1/internal/news/reconcile (empty body)
GET /api/v1/internal/news/reconcile/status?jobId=...
Poll the status endpoint for the result. It uses event-vector similarity followed by Ollama verification before merging duplicate event records, splitting events that contain distinct topics, and regenerating the resulting summaries.
Run expiration cleanup daily:
POST /api/v1/internal/news/expire
If the dashboard shows no events despite related articles, score every active event against each visibility gate with:
GET /api/v1/internal/news/visibility
It reports how many events pass recency, expiry, member-count, confidence, and unread filters, plus per-event details for the largest events.
It marks past-due events expired and removes expired records after the default 30-day retention period.
Backfill title and content for unsorted and recommended URL items with:
POST /api/v1/internal/unsorted/backfill
The endpoint is safe to rerun; items with existing content and items with medium
conversation or status are skipped. It processes pending and failed entries in
both unsorted and collection recommendations. Migration 0042_reset_webpage_content
clears existing content for eligible URL items once so the next scheduled backfill
re-extracts them as Markdown.
Re-evaluate existing unsorted items against all writable collections with:
POST /api/v1/internal/collections/recommend
This runs as a background job and can create recommendations for multiple collections from one item. Poll the returned job ID with the news process status endpoint. Items are only re-evaluated when they have not been recommended yet or when a writable collection has changed, so this endpoint can safely run from a regular cron job. The same run refreshes suggested clusters and cached Semble collection links.
Reconcile self-authored activity and synchronize engagement-derived models with:
POST /api/v1/internal/engagement/sync
Run this endpoint in the 15-minute maintenance sequence so new engagement affects ranking promptly.
Run daily interest lifecycle maintenance and expire old dashboard items with:
POST /api/v1/internal/sync-interests
Client-facing routes only record the immediate user action and do not perform this maintenance.