A self-hosted, privacy-first manager workspace for organizing people context, 1:1 history, development goals, action items, and kudos. CrewCaptain is not HR software — it's a private cockpit for people-centric leadership.
Heads up — please read before relying on this. CrewCaptain was built with the help of AI (Kiro) and is maintained by a single developer in their spare time. I have every intention of fixing bugs and addressing issues, but my free time is limited, so there are no guarantees on response or fix timelines. Treat this as a best-effort, as-is open-source project (see LICENSE — no warranty) and review the code yourself before depending on it for anything important.
- Person Directory — Full CRUD for team members with name, preferred name, role title, timezone, start date, email, and tags
- Morale Tracking — Visual morale indicators (Green/Yellow/Red/Unknown) per person with optional notes
- Pinned Remember Items — Add, remove, and reorder quick-reference notes per person
- Sticky Notes — Visual sticky-note cards replacing the basic pinned items list. Each note has a color (6 options: cyan, purple, green, amber, pink, slate), optional tag label, sensitive flag, and supports inline edit, drag-and-drop reorder, and delete with undo (10s toast). Person detail shows a card grid with truncation at 100 chars; People list shows up to 2 non-sensitive previews per card. Starter templates for quick capture (Family, Docs, Link, Life event, Manager). Markdown export includes sticky notes with sensitive content masked. Soft limit of 10 notes with friendly warning.
- Filtering & Pagination — Filter people by tag or morale status with paginated results
- At-a-Glance Summary — Person detail includes last 1:1 date, open action items count, and active PDP goals (placeholder)
- OIDC Authentication — Secure login via authentik (OAuth2/OIDC) with automatic user provisioning
- Data Isolation — All queries scoped by authenticated user (manager) — no cross-user data access
- Frontend UI — People list, person detail, create person pages with filter bar, morale indicators, and empty states
- Cyberpunk-Lite Design System — Dark-first UI with electric cyan/neon violet accents, JetBrains Mono headings, glassmorphism cards, glow effects, Inter body text, CSS custom properties design tokens, and consistent navigation
- 1:1 Entry Management — Full-stack series configuration (cadence + template), entry CRUD with agenda items, Markdown notes, outcomes, sensitive flag, paginated timeline, template prefill, and person at-a-glance last 1:1 date. Prep Notes Panel: collapsible section on the 1:1 entry page (create + edit) that surfaces INBOX quick notes assigned to the person as "talking points to discuss." One-click "Add to Agenda" attaches the note to the 1:1 entry; "Dismiss" archives it. Hidden when empty.
- Action Items — Create, track, complete, and cancel follow-ups from 1:1s with per-person and cross-person views, overdue filtering, owner type (manager/person), due dates, and status transitions (OPEN → DONE, OPEN → CANCELED). Full frontend with action items tab on person detail, status filter, inline create/edit forms, and cyberpunk-themed components. Inline action items section on the 1:1 entry page allows quick-adding action items during a session (auto-linked to the entry), viewing existing open items for the person, and marking items done — all without leaving the 1:1 page.
- PDP Goal Tracking — Personal development plans per person with goals (title, description, target date), status transitions (ACTIVE → ACHIEVED/PAUSED/DROPPED, PAUSED → ACTIVE), timestamped progress updates with sensitive flag, and full frontend with PDP goals tab, status filter, inline create/edit forms, and cyberpunk-themed components.
- Kudos / Recognition — Record positive feedback and achievements per person with date, Markdown text, and optional tags (e.g., "impact", "collaboration"). Full frontend with Kudos tab on person detail, inline create form, and delete. Immutable entries (create + delete only).
- Quick Notes (Inbox) — Global quick capture for thoughts, follow-ups, and observations. Notes can be unassigned (inbox), assigned to a person, or self-assigned (personal notes for the manager). Status workflow: INBOX → ATTACHED (to 1:1) / CONVERTED (to action item) / ARCHIVED. Supports sensitive flag. Self-assigned notes are accessible via "My Notes" in the user menu. Invariant: a note cannot be both self-assigned and assigned to a person — assigning to a person clears the self-assigned flag. Full frontend with dedicated Quick Notes page, My Notes page, status filter, and inline create form. Quick Note Overlay — Global floating overlay accessible from any page via
Ctrl+Shift+Qor the floating action button (bottom-right). Cyberpunk glassmorphism design with neon glow fade-in animation, scan-line texture, and slide-up entrance. SupportsCtrl+Enterto save. Auto-closes after successful capture. Dismissible via Escape, backdrop click, or cancel button. Respectsprefers-reduced-motion. - Dashboard — At-a-glance overview showing overdue action items, due-soon items, stale 1:1 reminders (based on cadence), and upcoming work anniversaries. Configurable lookahead windows for due-soon (default 3 days) and anniversaries (default 30 days).
- Sensitive Content Encryption — Application-level AES-256-GCM encryption for sensitive text fields at rest. When
ENCRYPTION_KEYis configured, all content markedsensitive=true(1:1 notes/outcomes, quick notes, PDP updates) is encrypted before storage and decrypted on read. Graceful fallback: without a key, the system operates normally with plaintext storage. Supports legacy unencrypted data migration (reads both encrypted and unencrypted content). - In-App Notifications — Scheduled notification generation for overdue action items, due-soon items (configurable threshold, default 3 days), stale 1:1 reminders (based on cadence), and upcoming work anniversaries (7-day lookahead). Notification center with bell icon in navigation, unread badge, mark-as-read (individual and bulk), and dedicated notifications page with pagination and unread filter. Deduplication prevents duplicate notifications within 24 hours. Scheduler runs hourly by default (configurable via cron expression).
- Full-Text Search — Search across all manager data (people, 1:1 notes, quick notes, action items, PDP goals, kudos) using PostgreSQL full-text search with GIN indexes and relevance ranking. Type filters, pagination, and sensitive content protection (sensitive snippets hidden in results). Dedicated search page with real-time URL state and navigation link.
- Per-Person Markdown Export — Export all data for a person as a structured Markdown file: profile summary, pinned remember items, morale, 1:1 history (reverse chronological), action items (grouped by status), PDP goals with progress updates, and kudos. Optional date range filter. Sensitive content is marked but not exposed. Download via Export button on person detail page.
- Gamification & Engagement — Dashboard gamification elements for engagement: animated progress ring for PDP goal completion percentage, 1:1 streak counter (consecutive weeks with meetings), achievement badges for milestones (first 1:1, 10 action items closed, etc.), and activity heatmap (contribution-graph style). Micro-animation on task completion (checkmark with glow burst). All animations respect
prefers-reduced-motion. - User Settings — Per-user persistent settings page with: theme selection (dark/light), dashboard reminder thresholds (due-soon days, stale 1:1 days, anniversary lookahead), notification type toggles (overdue, due-soon, stale 1:1, anniversary), and achievement visibility toggle. Settings are stored in the database and respected by the notification scheduler and dashboard.
- Light Theme — Full light theme alternative to the default cyberpunk dark theme. Clean surfaces, teal/purple accents, proper contrast ratios, and subtle shadows instead of glows. Toggled via Settings page.
- Review Packet Generator — Generate structured review/performance summary documents for a person over a configurable date range. Includes executive summary with statistics (1:1 count, action item completion rate, PDP goal progress, kudos count), morale status, detailed 1:1 meeting history, action items grouped by status, PDP goals with progress updates, and kudos with tag summary. Sensitive content is excluded. Download as Markdown via "Review Packet" button on person detail page.
- Bulk Import (CSV) — Import multiple people at once from a CSV file. Supports columns: name (required), preferred_name, role_title, timezone, start_date (YYYY-MM-DD), email, tags (pipe-separated). Preview before import, per-row error reporting, max 500 rows per import. Accessible via "Import CSV" button on the People list page.
- Soft-Delete + Restore — Deleting a person moves them to trash (soft-delete) instead of permanently removing them. Trash page shows all deleted people with restore and permanent delete capability. All queries automatically exclude soft-deleted records. Data isolation enforced on trash operations. Permanent delete requires confirmation and cascades to all associated data (1:1 entries, action items, PDP goals, kudos).
- Audit Log — Records key actions (create, update, delete, restore) across all entities for the manager's own traceability. Paginated audit log page with entity type and action filters. All entries scoped by userId. Accessible via user menu in navigation.
- Workspaces — Lightweight organizational containers for grouping people (e.g., "My Team", "Mentees", "Skip-levels"). A workspace belongs to a single manager (private, no sharing). A person belongs to one workspace (optional). Opt-in: if no workspaces exist, everything works as before. Includes workspace CRUD, person-to-workspace assignment, workspace filter on People list, and management page accessible via user menu.
- Landing Page — Modern, high-converting landing page with cyberpunk-lite dark theme. Hero section with HUD visual motif, feature cards with glassmorphism, dedicated AI features section highlighting all AI capabilities (agenda generation, outcome extraction, performance narrative, kudos refinement, SMART goal check, and configurable AI settings), interactive screenshot showcase (tabbed gallery with 11 views including AI-powered features, quick capture, and settings), 3-step deployment guide, privacy/self-hosted messaging, and dual CTA sections. Fully responsive, accessible (WCAG AA), respects
prefers-reduced-motion. Authenticated users are redirected to the dashboard. - Prometheus Metrics — Exposes application metrics at
/actuator/prometheusfor Prometheus scraping. Secured with a bearer token (METRICS_TOKEN). Includes JVM metrics, HTTP request metrics, HikariCP connection pool stats, and custom 1:1 metrics (total entries, entries in last 7 days). Health endpoint at/actuator/healthremains unauthenticated for Docker healthchecks. - AI-Powered 1:1 Prep Assistant — Optional AI assistant that synthesizes person-specific context (recent 1:1 notes, open action items, active PDP goals, recent kudos) and generates 3-5 suggested agenda items for the next meeting. Configurable per-user in Settings: API Base URL (any OpenAI-compatible endpoint — Ollama, LiteLLM, OpenAI, etc.), API Key, Model Name, and Privacy Mode toggle. When Privacy Mode is ON, content marked
sensitive=trueis never sent to the LLM. Graceful failure: if the API is unreachable, shows an inline error without breaking the 1:1 page. One-click add suggestions to the agenda. Cyberpunk-themed UI with glow burst animation on completion and pulse loading state. Respectsprefers-reduced-motion. - AI Performance Narrative Generator — Generate AI-powered performance review narratives from historical data. Aggregates kudos (with tags), PDP goals (with status and non-sensitive updates), 1:1 outcomes, and action item completion stats within a configurable date range. Sends context to the user's configured LLM with a leadership-coach system prompt. Three writing styles: Narrative (3 paragraphs), Bullet Points (structured sections), Concise (1 paragraph). Privacy Mode respected — sensitive content excluded unless disabled. Result displayed in an editable textarea with copy-to-clipboard. Button only visible when AI is enabled in Settings. Cyberpunk pulse animation during generation.
- AI Coaching & Feedback Refinement — AI-powered coaching tools integrated into Kudos and PDP Goals. Kudos Refinement: "Refine" button on the Kudos form sends the draft to the LLM using the SBI (Situation-Behavior-Impact) framework, returning a polished version in a comparison view (Apply/Keep Original). PDP Goal SMART Check: "SMART Check" button on the PDP Goal form evaluates the goal against SMART criteria and suggests an improved title and description. Customizable Prompts: All AI system prompts (Kudos Refinement, PDP Optimization, Agenda Prep, Narrative) are configurable per-user in Settings under the "AI Prompts" section, with "Reset to Default" buttons. Existing AI features (1:1 Prep, Narrative Generator) now use the user's custom prompts when set.
- AI Outcome Extractor — Post-meeting productivity tool that parses 1:1 entry notes using the configured LLM to extract action items and key decisions. Identifies tasks for both Manager and Direct Report with inferred due dates. Presents results in a review modal where items can be edited, toggled, or deselected before bulk-applying. Action items are created with
originatingEntryIdlinking them to the source entry. Decisions are appended to the entry's Outcomes field. Duplicate detection: items matching existing action item titles are flagged and pre-unchecked. Privacy: disabled for sensitive entries when AI Privacy Mode is ON. Custom prompt configurable in Settings. Cyberpunk-themed modal with owner-type color coding (cyan for Manager, violet for Person). - AI Strategic Trend Radar — Diagnostic tool that analyzes 90 days of team member data (1:1 outcomes, action items, PDP progress, kudos) to surface long-term patterns and momentum shifts. Evaluates four dimensions: Sentiment/Morale Drift, Work/Growth Balance, Recognition Velocity, and Meeting Efficacy. Each insight includes a Confidence Score (0-100%) based on data volume and recency: Low (<40%, insufficient data), Moderate (40-75%, some signal), High (>75%, strong signal). Minimum 2 meetings required to generate insights; shows "Scanning horizon..." empty state otherwise. Privacy Mode respected — outcomes excluded when enabled. Custom system prompt configurable in Settings. Cyberpunk glassmorphism insight cards with neon confidence gauges and dimension icons. New "✦ Insights" tab on Person Detail page (conditionally visible when AI enabled).
- Strategy Hub — Strategic layer for managers to define high-level objectives and visualize team PDP goal alignment. Create strategy goals with title, description, target date, and sensitive flag. Link PDP goals to strategy goals to track alignment. Alignment scoring shows percentage of active PDP goals contributing to each strategy goal. Gap analysis panel highlights unlinked PDP goals and strategy goals without contributors. Full CRUD with status transitions (ACTIVE → ACHIEVED/DROPPED). AES-256-GCM encryption for sensitive strategy goals. Full-text search integration with GIN index. Comprehensive audit logging for all CRUD and link/unlink operations.
- Unified Triage Queue — Centralized actionable inbox aggregating overdue action items, due-soon items, stale 1:1 reminders, and upcoming work anniversaries into a single prioritized list. Sorted by criticality (Overdue > Due Soon > Stale > Informational), then chronologically. Filtering by scope (All/Mine), item type, workspace, and person. Vim-inspired keyboard navigation (j/k to move, d=done, c=cancel, s=snooze, a=add to 1:1, q=quick note, r=reassign, t=set due, Enter=peek drawer). Full InlineActionMenu with Done, Cancel, Snooze (1d/3d/7d submenu), Reassign Owner, Set Due Date (inline date picker), Add to 1:1, Save as Quick Note. QuickPeekDrawer (Enter key or peek icon) shows person context: morale dot, role, last 1:1 summary, open action items, and recent kudos — all without navigating away. Snooze support hides items temporarily. Toggle Owner switches between Manager/Person. AI "Next Best Action" hints (one-sentence suggestions from configured LLM, privacy mode respected, configurable prompt in Settings). Sensitive content masked in list. Workspace chip shown on rows. Glassmorphism card UI with cyberpunk glow ring on selected row. Global shortcut Cmd/Ctrl+J focuses the queue. Empty state with "You're all clear" messaging. Accessible via Navigation bar or
/triageroute. - AI Command Terminal — Natural language command overlay for creating action items, kudos, quick notes, and 1:1 entries via AI-powered parsing. Accessible via
Cmd/Ctrl+Kor floating action button (purple ⌘ icon, positioned next to Quick Note FAB). The terminal sends user input to the configured LLM (Ollama, OpenAI-compatible) with a structured JSON system prompt and the person directory context, then parses the response into typed commands. Supports intents:create_action_item,create_kudo,create_quick_note,create_one_on_one_entry. For 1:1 entries, the AI extracts meeting notes and a meeting date (defaulting to today if unspecified). Two execution modes: Standard (Confirm & Save) shows a preview card requiring manual confirmation, Auto-Execute (configurable in Settings) bypasses the preview and instantly executes with a 10-second undo toast. Privacy Mode: if enabled and the AI detects sensitive content, an explicit warning is displayed before execution. Sensitive items are routed through the AES-256-GCM encryption pipeline. Built-inhelpcommand (type "help" anytime to redisplay available commands without calling the AI). Cyberpunk glassmorphism terminal panel with slide-up animation, violet accent theme, continuous scrolling chat interface, scan-line texture, and monospace terminal aesthetic. Respectsprefers-reduced-motion. Only visible when AI is enabled and configured in User Settings. Backend:POST /api/v1/ai/command(parse command),GET /api/v1/ai/command/directory(person directory for micro-context injection). Custom system prompt configurable in Settings as "Command Terminal Prompt". - AI Admin/Team Defaults — Admins can provide team-wide AI configuration via environment variables (
AI_DEFAULT_BASE_URL,AI_DEFAULT_API_KEY,AI_DEFAULT_MODEL). Users who haven't configured their own AI settings automatically use the team defaults. Users who configure their own AI server/model in Settings override the team defaults. The Settings page shows a badge indicating the active config source ("AI available via team defaults" or "Using your personal AI config").GET /api/v1/settings/ai-statusreturns the resolved AI availability and config source. All AI features (Command Terminal, Prep Assistant, Narrative, Coaching, Outcome Extractor, Trend Radar, Link Suggestions, Triage Hints) are shown whenever AI is effectively available — i.e. when either the user has their own config or admin team defaults are set. The frontend gates feature visibility on the resolvedaiAvailableflag (not the user's personalaiEnabledtoggle), so setting only the admin defaults lights up the features for everyone. When neither source is configured, all AI features are hidden.
- (none currently)
| Layer | Technology |
|---|---|
| Backend | Kotlin + Spring Boot 3.3.5 (Hexagonal/DDD) |
| Frontend | Next.js 14 + React 18 + Auth.js (OIDC) |
| Database | PostgreSQL 16 |
| Auth | OAuth2 / OIDC via authentik |
| Deployment | Docker Compose |
| API Style | REST + JSON |
| Migrations | Flyway |
| Testing | JUnit 5 + Kotest + Testcontainers (backend), Jest + React Testing Library (frontend) |
- Docker 24+ and Docker Compose v2+
- Java 21 (for local backend development)
- Node.js 20+ and npm (for local frontend development)
- authentik instance (or any OIDC provider) for authentication — or use the bundled local authentik via the dev-auth overlay (see Local OAuth (Authentik))
Just want to kick the tyres? docker-compose.full-demo.yml is a single,
self-contained file that runs the entire stack — CrewCaptain, PostgreSQL, a
preconfigured authentik (login), and a local Ollama (AI) — with baked-in demo
defaults. No repo checkout, no .env, no identity provider setup.
You only need this one file. Download it, then:
# 1. One-time: add this to your hosts file so the browser and containers
# resolve authentik at the same hostname (keeps the OIDC issuer consistent).
# Linux/macOS: /etc/hosts — Windows: C:\Windows\System32\drivers\etc\hosts
echo "127.0.0.1 authentik" | sudo tee -a /etc/hosts
# 2. Start everything (first boot pulls images + a ~1 GB AI model)
docker compose -f docker-compose.full-demo.yml up
# 3. Open http://localhost:3000 and sign in with:
# username: demo
# password: demo12345First boot takes a minute or two while authentik migrates its database and Ollama downloads the model; the app stays up while that happens, and AI/login light up once they finish.
⚠️ Demo only. This file ships well-known, committed secrets so it works out of the box. Never expose it to the internet or use it in production — usedocker-compose.ymlwith your own secrets and a real OIDC provider instead.
The production docker-compose.yml pulls pre-built images from the container registry:
# 1. Clone the repository
git clone https://github.com/hechi/crewcaptain.git # or: git@github.com:hechi/crewcaptain.git
cd crewcaptain
# 2. Configure environment variables
cp .env.example .env
# Edit .env with your actual values (database credentials, OIDC settings)
# 3. Start the full stack (pulls images from registry)
docker compose up -d
# 4. Access the application
# Frontend: http://localhost:3000
# API: http://localhost:8080Images:
ghcr.io/hechi/crewcaptain/api:latestghcr.io/hechi/crewcaptain/frontend:latest
These public images are built and published by the GitHub Actions pipeline on every push to main.
The docker-compose.override.yml adds build directives and dev tooling. When present, docker compose up will build from source:
# Copy the example override file
cp docker-compose.override.example.yml docker-compose.override.yml
# Build and start with local source (override is auto-loaded)
docker compose up --buildThe override exposes the database port (5432), mounts source volumes for hot-reload, and sets development environment variables.
CrewCaptain authenticates via OIDC, so trying it out normally requires an external identity provider. For development and evaluation, the docker-compose.dev-auth.yml overlay runs a self-contained authentik instance that is preconfigured with a demo user and a ready-to-use OIDC application — no clicking through setup wizards.
⚠️ Development only. This overlay ships well-known, committed credentials (demo/demo12345) via a blueprint. Never enable it in production — use your own real authentik (or any OIDC provider) there.
One-time setup — add this line to your /etc/hosts so the browser and the containers resolve authentik at the same hostname (this is what keeps the OIDC token issuer consistent):
127.0.0.1 authentik
Start the stack with the overlay:
docker compose -f docker-compose.yml -f docker-compose.dev-auth.yml upFirst boot takes ~30–60s while authentik migrates its database and applies the bootstrap blueprint. Then:
- App: http://localhost:3000 — sign in with
demo/demo12345 - authentik admin: http://authentik:9000/if/admin/ — sign in with
akadmin/admin12345
The demo OIDC application (client_id: crewcaptain), the demo user, and the provider are all created automatically by authentik/blueprints/crewcaptain-dev.yaml. Default secrets can be overridden via the AUTHENTIK_* variables documented in .env.example.
Note: The frontend container sets
AUTH_TRUST_HOST=true(indocker-compose.yml). Auth.js v5 requires this when running self-hosted behind Docker/a proxy; without it you'll seeUntrustedHost: Host must be trusted. If you change compose env, recreate the container withdocker compose ... up -d --force-recreate frontendso it's picked up.
CrewCaptain's AI features (1:1 prep suggestions, summaries, performance narratives, etc.) talk to any OpenAI-compatible endpoint. For development and evaluation, the docker-compose.ai.yml overlay runs a self-contained Ollama instance that automatically pulls a small model on first start and wires it in as the team-wide AI default — so the AI features work out of the box with no external API key.
By default it pulls qwen2.5:1.5b (~1 GB), a small instruction-following model that's enough to demonstrate summaries and suggestions and runs on CPU. It's a demo-grade model — fine for kicking the tyres, not for production-quality output.
Start the stack with the overlay:
docker compose -f docker-compose.yml -f docker-compose.ai.yml upOr bring up local auth and local AI together:
docker compose -f docker-compose.yml \
-f docker-compose.dev-auth.yml \
-f docker-compose.ai.yml upOn first start the ollama-init helper downloads the model (~1 GB, one-time — stored in the ollama-models volume). AI calls fail gracefully until the pull finishes, so nothing crashes while it downloads. Once ready, AI features light up automatically for all users (via AI_DEFAULT_*); users can still override with their own provider in Settings.
Tuning:
- Smaller/faster model:
OLLAMA_MODEL=gemma3:1b docker compose -f docker-compose.yml -f docker-compose.ai.yml up - GPU acceleration: uncomment the
deploy.resourcesblock indocker-compose.ai.yml(requires the NVIDIA Container Toolkit). - Image tag: override with
OLLAMA_TAG.
⚠️ This overlay is for development/evaluation. CPU inference on a tiny model is slow and low-quality compared to a hosted model; for real deployments, pointAI_DEFAULT_*(or per-user Settings) at a production-grade LLM endpoint.
CrewCaptain uses dev.sh as the primary local development runner:
# Start the backend (requires Java 21, PostgreSQL running)
./dev.sh backend
# Start the frontend (requires Node.js 20+)
./dev.sh frontendThe script handles dependency installation, database migrations (Flyway), and starts services with hot-reload enabled.
Backend runs on http://localhost:8080
Frontend runs on http://localhost:3000
All endpoints require Authorization: Bearer <jwt> header. Base path: /api/v1/
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/persons |
Create a new person |
| GET | /api/v1/persons |
List persons (paginated) |
| GET | /api/v1/persons/{id} |
Get person by ID |
| PUT | /api/v1/persons/{id} |
Update a person |
| DELETE | /api/v1/persons/{id} |
Soft-delete a person (move to trash) |
| POST | /api/v1/persons/{id}/restore |
Restore a soft-deleted person |
| DELETE | /api/v1/persons/{id}/permanent |
Permanently delete a soft-deleted person |
| GET | /api/v1/persons/trash |
List deleted persons (paginated) |
| PUT | /api/v1/persons/{id}/morale |
Set morale status |
| POST | /api/v1/persons/{id}/remember-items |
Add a pinned remember item |
| PUT | /api/v1/persons/{id}/remember-items/{itemId} |
Update a remember item |
| DELETE | /api/v1/persons/{id}/remember-items/{itemId} |
Remove a remember item |
| PUT | /api/v1/persons/{id}/remember-items/reorder |
Reorder remember items |
| GET | /api/v1/persons/{id}/export |
Export person data as Markdown |
| GET | /api/v1/persons/{id}/review-packet |
Generate review packet as Markdown |
| POST | /api/v1/persons/{id}/ai-narrative |
Generate AI performance narrative |
| POST | /api/v1/persons/{personId}/ai-prep |
Generate AI 1:1 agenda suggestions |
| POST | /api/v1/persons/{personId}/ai-trend-radar |
Generate AI strategic trend insights |
| POST | /api/v1/persons/{personId}/one-on-one-entries/{entryId}/extract-outcomes |
AI-extract action items and decisions from notes |
| POST | /api/v1/persons/{personId}/one-on-one-entries/{entryId}/apply-outcomes |
Bulk-create extracted action items and append decisions |
| POST | /api/v1/persons/import |
Bulk import persons from CSV |
Export query parameters:
dateFrom— Optional start date filter (ISO 8601 date, e.g., 2024-01-01)dateTo— Optional end date filter (ISO 8601 date, e.g., 2024-12-31)
Export response:
- Content-Type:
text/markdown; charset=UTF-8 - Content-Disposition:
attachment; filename="export.md" - Body: Structured Markdown with profile, remember items, morale, 1:1 history, action items, PDP goals, and kudos
Review packet query parameters (both required):
dateFrom— Start date of review period (ISO 8601 date, e.g., 2024-01-01)dateTo— End date of review period (ISO 8601 date, e.g., 2024-06-30)
Review packet response:
- Content-Type:
text/markdown; charset=UTF-8 - Content-Disposition:
attachment; filename="review-packet.md" - Body: Structured Markdown with executive summary (statistics), morale, 1:1 meetings, action items (grouped by status with completion rate), PDP goals with progress, and kudos with tag summary. Sensitive content is excluded.
AI Narrative (POST /api/v1/persons/{id}/ai-narrative):
- Request body:
{ "dateFrom": "2026-01-01", "dateTo": "2026-06-30" } - Response:
{ "narrative": "Generated text...", "error": null }or{ "narrative": null, "error": "Error message" } - Requires AI to be enabled and configured in user settings
- Respects Privacy Mode (excludes sensitive content when enabled)
- Writing style controlled by
aiWritingStyleuser setting (NARRATIVE, BULLET_POINTS, CONCISE)
Bulk import (POST /api/v1/persons/import):
- Content-Type:
multipart/form-data - Form field:
file— CSV file with header row - Supported CSV columns:
name(required),preferred_name,role_title,timezone,start_date(YYYY-MM-DD),email,tags(pipe-separated, e.g., "engineering|senior") - Maximum 500 rows per import
- Response:
{ "successCount": 2, "errorCount": 1, "errors": ["Row 3: Name must not be blank"] } - Partial success: valid rows are imported even if some rows have errors
Query parameters for the persons list endpoint:
page— Page number (default: 0)size— Page size (default: 20)tag— Filter by tagmorale— Filter by morale status (GREEN, YELLOW, RED, UNKNOWN)workspace— Filter by workspace UUID
| Method | Endpoint | Description |
|---|---|---|
| PUT | /api/v1/persons/{personId}/one-on-one-series |
Create/update 1:1 series config |
| GET | /api/v1/persons/{personId}/one-on-one-series |
Get 1:1 series config |
| POST | /api/v1/persons/{personId}/one-on-one-entries |
Create a 1:1 entry |
| GET | /api/v1/persons/{personId}/one-on-one-entries |
List 1:1 entries (paginated) |
| GET | /api/v1/persons/{personId}/one-on-one-entries/{entryId} |
Get a 1:1 entry |
| PUT | /api/v1/persons/{personId}/one-on-one-entries/{entryId} |
Update a 1:1 entry |
| DELETE | /api/v1/persons/{personId}/one-on-one-entries/{entryId} |
Delete a 1:1 entry |
1:1 Series fields:
cadenceType— WEEKLY, BIWEEKLY, MONTHLY, or CUSTOMcustomIntervalDays— Required when cadenceType is CUSTOM (positive integer)templateMarkdown— Markdown template to prefill new entries
1:1 Entry fields:
meetingDate— Required (ISO 8601 timestamp)agendaItems— List of{ text, checked }objectsnotesMarkdown— Markdown notes (prefilled from template if not provided)outcomesMarkdown— Markdown outcomes/decisionssensitive— Boolean flag for sensitive content (default: false)
Query parameters for the entries list endpoint:
page— Page number (default: 0)size— Page size (default: 20)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/persons/{personId}/action-items |
Create an action item |
| GET | /api/v1/persons/{personId}/action-items |
List action items for a person |
| GET | /api/v1/persons/{personId}/action-items/{actionItemId} |
Get an action item |
| PUT | /api/v1/persons/{personId}/action-items/{actionItemId} |
Update an action item |
| POST | /api/v1/persons/{personId}/action-items/{actionItemId}/complete |
Mark action item as DONE |
| POST | /api/v1/persons/{personId}/action-items/{actionItemId}/cancel |
Mark action item as CANCELED |
| DELETE | /api/v1/persons/{personId}/action-items/{actionItemId} |
Delete an action item |
| GET | /api/v1/action-items |
List all action items (cross-person) |
Action Item fields:
title— Required (max 500 chars)description— Optional textownerType— MANAGER or PERSON (default: MANAGER)dueDate— Optional date (ISO 8601 date, e.g., 2026-05-20)originatingEntryId— Optional UUID linking to a 1:1 entry
Query parameters for action items list endpoints:
page— Page number (default: 0)size— Page size (default: 20)status— Filter by status (OPEN, DONE, CANCELED)originatingEntryId— Filter by originating 1:1 entry UUID (per-person endpoint only)overdueOnly— Only return overdue items (cross-person endpoint only, default: false)
Status transitions:
- OPEN → DONE (via
/complete) - OPEN → CANCELED (via
/cancel) - No other transitions are allowed
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/persons/{personId}/pdp-goals |
Create a PDP goal |
| GET | /api/v1/persons/{personId}/pdp-goals |
List PDP goals for a person |
| GET | /api/v1/persons/{personId}/pdp-goals/{goalId} |
Get a PDP goal |
| PUT | /api/v1/persons/{personId}/pdp-goals/{goalId} |
Update a PDP goal |
| POST | /api/v1/persons/{personId}/pdp-goals/{goalId}/achieve |
Mark goal as ACHIEVED |
| POST | /api/v1/persons/{personId}/pdp-goals/{goalId}/pause |
Mark goal as PAUSED |
| POST | /api/v1/persons/{personId}/pdp-goals/{goalId}/drop |
Mark goal as DROPPED |
| POST | /api/v1/persons/{personId}/pdp-goals/{goalId}/resume |
Resume a PAUSED goal to ACTIVE |
| DELETE | /api/v1/persons/{personId}/pdp-goals/{goalId} |
Delete a PDP goal |
| POST | /api/v1/persons/{personId}/pdp-goals/{goalId}/updates |
Add a progress update |
| GET | /api/v1/persons/{personId}/pdp-goals/{goalId}/updates |
List progress updates |
| DELETE | /api/v1/persons/{personId}/pdp-goals/{goalId}/updates/{updateId} |
Delete a progress update |
PDP Goal fields:
title— Required (max 500 chars)description— Optional texttargetDate— Optional date (ISO 8601 date)
PDP Goal status transitions:
- ACTIVE → ACHIEVED (via
/achieve) - ACTIVE → PAUSED (via
/pause) - ACTIVE → DROPPED (via
/drop) - PAUSED → ACTIVE (via
/resume) - No other transitions are allowed
PDP Update fields:
textMarkdown— Required (Markdown text)sensitive— Optional boolean (default: false)
Query parameters for PDP goals list endpoint:
page— Page number (default: 0)size— Page size (default: 20)status— Filter by status (ACTIVE, ACHIEVED, PAUSED, DROPPED)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/persons/{personId}/kudos |
Create a kudos entry |
| GET | /api/v1/persons/{personId}/kudos |
List kudos for a person (paginated) |
| GET | /api/v1/persons/{personId}/kudos/{kudosId} |
Get a kudos entry |
| DELETE | /api/v1/persons/{personId}/kudos/{kudosId} |
Delete a kudos entry |
| GET | /api/v1/kudos |
List all kudos (cross-person) |
Kudos fields:
text— Required (Markdown text)date— Optional date (ISO 8601 date, defaults to today)tags— Optional list of strings (e.g., ["impact", "collaboration"])
Query parameters for kudos list endpoints:
page— Page number (default: 0)size— Page size (default: 20)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/quick-notes |
Create a quick note |
| GET | /api/v1/quick-notes |
List quick notes (paginated) |
| GET | /api/v1/quick-notes/{quickNoteId} |
Get a quick note |
| PUT | /api/v1/quick-notes/{quickNoteId} |
Update a quick note |
| DELETE | /api/v1/quick-notes/{quickNoteId} |
Delete a quick note |
| POST | /api/v1/quick-notes/{quickNoteId}/assign |
Assign to a person |
| POST | /api/v1/quick-notes/{quickNoteId}/assign-self |
Assign to self (personal note) |
| POST | /api/v1/quick-notes/{quickNoteId}/attach |
Attach to a 1:1 entry |
| POST | /api/v1/quick-notes/{quickNoteId}/convert |
Mark as converted (to action item) |
| POST | /api/v1/quick-notes/{quickNoteId}/archive |
Archive the quick note |
Quick Note fields:
text— Required (Markdown text)personId— Optional UUID to assign to a personsensitive— Optional boolean (default: false)selfAssigned— Optional boolean (default: false). When true, the note is a personal note for the manager. Mutually exclusive withpersonId.
Quick Note status transitions:
- INBOX → ATTACHED (via
/attachwithentryId— links to a specific 1:1 entry) - INBOX → CONVERTED (via
/convert) - INBOX → ARCHIVED (via
/archive) - No other transitions are allowed
Query parameters for the quick notes list endpoint:
page— Page number (default: 0)size— Page size (default: 20)status— Filter by status (INBOX, ATTACHED, CONVERTED, ARCHIVED)personId— Filter by assigned personselfAssigned— Filter by self-assigned flag (true/false)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/dashboard |
Get dashboard data (overdue, due-soon, stale 1:1s, anniversaries) |
Query parameters:
dueSoonDays— Number of days to look ahead for due-soon items (default: 3)anniversaryLookaheadDays— Number of days to look ahead for anniversaries (default: 30)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/notifications |
List notifications (paginated) |
| GET | /api/v1/notifications/unread-count |
Get unread notification count |
| POST | /api/v1/notifications/{notificationId}/read |
Mark a notification as read |
| POST | /api/v1/notifications/read-all |
Mark all notifications as read |
Query parameters for the notifications list:
page— Page number (default: 0)size— Page size (default: 20)unreadOnly— Only return unread notifications (default: false)
Notification types:
ACTION_ITEM_OVERDUE— Action item past its due dateACTION_ITEM_DUE_SOON— Action item due within the configured thresholdSTALE_ONE_ON_ONE— 1:1 meeting overdue based on cadenceUPCOMING_ANNIVERSARY— Work anniversary approaching
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/search |
Full-text search across all manager data |
Query parameters:
q— Search query (required)type— Filter by result type (repeatable): PERSON, ONE_ON_ONE_ENTRY, QUICK_NOTE, ACTION_ITEM, PDP_GOAL, PDP_UPDATE, KUDOS, STRATEGY_GOALpage— Page number (default: 0)size— Page size (default: 20, max: 100)
Notes:
- All results are scoped by the authenticated user (security invariant)
- Sensitive content snippets are hidden in search results (only title shown)
- Uses PostgreSQL full-text search with GIN indexes, prefix matching, and relevance ranking
- Encrypted sensitive fields are not searchable (trade-off for encryption at rest)
- Strategy Goals follow the same rule: when a strategy goal is marked sensitive=true, its title and description are encrypted at rest and excluded from full-text search and GIN indexes. Queries against encrypted fields return no results by design.
Response fields:
results— Array of search results with id, type, title, snippet, personId, personName, sensitive, createdAt, relevanceScorequery— The original search querytotalCount— Total number of matching resultspage/size/totalPages— Pagination metadata
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/strategy-goals |
Create a strategy goal |
| GET | /api/v1/strategy-goals |
List strategy goals (paginated) |
| GET | /api/v1/strategy-goals/{id} |
Get a strategy goal |
| PUT | /api/v1/strategy-goals/{id} |
Update a strategy goal |
| DELETE | /api/v1/strategy-goals/{id} |
Delete a strategy goal |
| POST | /api/v1/strategy-goals/{id}/achieve |
Mark strategy goal as ACHIEVED |
| POST | /api/v1/strategy-goals/{id}/drop |
Mark strategy goal as DROPPED |
| POST | /api/v1/strategy-goals/{id}/links |
Link a PDP goal to strategy goal |
| GET | /api/v1/strategy-goals/{id}/links |
Get linked PDP goals |
| DELETE | /api/v1/strategy-goals/{id}/links/{pdpGoalId} |
Unlink a PDP goal |
| GET | /api/v1/strategy-goals/{id}/alignment |
Get alignment score for goal |
| GET | /api/v1/strategy-goals/alignment |
Get all alignment scores |
| GET | /api/v1/strategy-goals/gap-analysis |
Get gap analysis |
| GET | /api/v1/persons/{personId}/pdp-goals/{pdpGoalId}/strategy-goals |
Get strategy goals linked to a PDP goal |
| POST | /api/v1/strategy-goals/ai-suggestions |
Generate AI link suggestions |
Strategy Goal fields:
title— Required (max 500 chars)description— Optional text (max 5000 chars)targetDate— Optional date (ISO 8601 date)status— ACTIVE, ACHIEVED, or DROPPED (default: ACTIVE)sensitive— Optional boolean (default: false)
Encryption/Search trade-off:
- When
sensitive=true, title and description are stored encrypted (AES-256-GCM) and decrypted on read. - Encrypted fields cannot be indexed or searched. Sensitive strategy goals are excluded from search results, and queries against encrypted values return no matches by design.
Strategy Goal status transitions:
- ACTIVE → ACHIEVED (via
/achieve) - ACTIVE → DROPPED (via
/drop) - No other transitions are allowed
Query parameters for strategy goals list:
page— Page number (default: 0)size— Page size (default: 20)status— Filter by status (ACTIVE, ACHIEVED, DROPPED)
Alignment Score fields:
strategyGoalId— Strategy goal UUIDstrategyGoalTitle— Strategy goal titletotalActivePdpGoals— Total number of active PDP goals across all peoplelinkedPdpGoals— Number of PDP goals linked to this strategy goalalignmentPercentage— Percentage of active PDP goals linked (0-100)
Gap Analysis fields:
unlinkedPdpGoals— Array of PDP goals not linked to any strategy goalemptyStrategyGoals— Array of strategy goals with no linked PDP goals
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/settings |
Get current user settings (returns defaults if none saved) |
| GET | /api/v1/settings/ai-status |
Get resolved AI availability and config source (personal vs team defaults) |
| PUT | /api/v1/settings |
Update user settings |
Settings fields:
dueSoonDays— Days before due date to show "due soon" (1–30, default: 3)staleOneOnOneDays— Days without a 1:1 before it's considered stale (1–90, default: 14)anniversaryLookaheadDays— Days to look ahead for anniversaries (1–90, default: 30)theme— UI theme:DARKorLIGHT(default: DARK)showAchievements— Show achievement badges on dashboard (default: true)notifyActionItemOverdue— Enable overdue action item notifications (default: true)notifyActionItemDueSoon— Enable due-soon action item notifications (default: true)notifyStaleOneOnOne— Enable stale 1:1 notifications (default: true)notifyUpcomingAnniversary— Enable anniversary notifications (default: true)
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/workspaces |
Create a workspace |
| GET | /api/v1/workspaces |
List workspaces |
| GET | /api/v1/workspaces/{workspaceId} |
Get a workspace |
| PUT | /api/v1/workspaces/{workspaceId} |
Update a workspace |
| DELETE | /api/v1/workspaces/{workspaceId} |
Delete a workspace |
| PUT | /api/v1/workspaces/persons/{personId}/workspace |
Assign a person to a workspace (or clear with empty body) |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/triage |
Get the unified triage queue |
| POST | /api/v1/triage/items/{itemId}/hint |
Get AI "next best action" hint for an item |
| POST | /api/v1/triage/persons/{personId}/action-items/{actionItemId}/snooze |
Snooze an action item |
Query parameters for the triage queue:
type— Filter by item type: ACTION_ITEM_OVERDUE, ACTION_ITEM_DUE_SOON, STALE_ONE_ON_ONE, UPCOMING_ANNIVERSARYworkspaceId— Filter by workspace (repeatable)personId— Filter by personscope— Owner scope:ALL(default) orMINE
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/gamification/stats |
Get gamification stats (PDP completion %, 1:1 streak, badges, activity heatmap) |
Query parameters:
heatmapDays— Number of days of activity history for the heatmap (default: 90)
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/audit-log |
List audit log entries (paginated) |
Query parameters:
entityType— Filter by entity type (e.g., PERSON, ACTION_ITEM, PDP_GOAL, ...)action— Filter by action (CREATE, UPDATE, DELETE, RESTORE, ...)page— Page number (default: 0)size— Page size (default: 20)
All AI endpoints require AI to be enabled and configured (personal config or team defaults) and respect Privacy Mode.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/ai/refine-kudos |
Refine kudos draft using the SBI framework |
| POST | /api/v1/ai/optimize-pdp-goal |
SMART-check and improve a PDP goal |
| POST | /api/v1/ai/optimize-strategy-goal |
Optimize a strategy goal |
| POST | /api/v1/ai/command |
Parse a natural-language command into typed actions |
| GET | /api/v1/ai/command/directory |
Get person directory for command micro-context |
Person-scoped AI endpoints (1:1 prep, performance narrative, trend radar, outcome extraction) are listed in the Person Directory table above.
| Variable | Description | Required |
|---|---|---|
DB_URL |
JDBC URL for PostgreSQL | Yes |
DB_USER |
Database username | Yes |
DB_PASSWORD |
Database password | Yes |
OIDC_ISSUER_URI |
OIDC issuer URI (authentik provider URL) | Yes |
OIDC_JWKS_URI |
JWKS endpoint URI | Yes |
ENCRYPTION_KEY |
Base64-encoded 32-byte AES key for sensitive field encryption (generate with: openssl rand -base64 32) |
No* |
NOTIFICATION_CRON |
Cron expression for notification scheduler (default: 0 0 * * * * — every hour) |
No |
NOTIFICATION_DUE_SOON_DAYS |
Days before due date to trigger "due soon" notifications (default: 3) | No |
NOTIFICATION_ANNIVERSARY_LOOKAHEAD_DAYS |
Days to look ahead for anniversary notifications (default: 7) | No |
METRICS_TOKEN |
Bearer token for securing the /actuator/prometheus metrics endpoint. If not set, the endpoint returns 403. Generate with: openssl rand -hex 32 |
No |
AI_DEFAULT_BASE_URL |
Base URL of the OpenAI-compatible AI API for team-wide defaults (e.g., http://ollama:11434/v1) |
No |
AI_DEFAULT_API_KEY |
API key for the team-wide AI provider (leave empty for local models like Ollama) | No |
AI_DEFAULT_MODEL |
Model name for team-wide AI defaults (e.g., llama3, gpt-4o) |
No |
| Variable | Description | Required |
|---|---|---|
NEXTAUTH_URL |
Canonical URL of the frontend | Yes |
NEXTAUTH_SECRET |
Auth.js session secret | Yes |
OIDC_CLIENT_ID |
OIDC client ID from authentik | Yes |
OIDC_CLIENT_SECRET |
OIDC client secret from authentik | Yes |
OIDC_ISSUER |
authentik OIDC issuer URL | Yes |
API_BASE_URL |
Internal URL to backend API (read at runtime, e.g., http://api:8080 for Docker or https://api.example.com for external) |
Yes |
*ENCRYPTION_KEY is required when sensitive field encryption is enabled. Generate with: openssl rand -base64 32. Must be exactly 32 bytes when Base64-decoded (256-bit AES key). Without this key, sensitive content is stored in plaintext (the sensitive flag still works for UI labeling).
AI Team Defaults: Set AI_DEFAULT_BASE_URL and AI_DEFAULT_MODEL to provide AI features to all users without requiring individual configuration. Users who set their own AI server/model in Settings will use their personal config instead. This allows admins to share a team subscription (e.g., a shared Ollama instance) while letting power users override with their own provider.
See .env.example for a complete template with placeholder values.
# Backend — all tests (requires Docker for Testcontainers)
cd api && ./gradlew test
# Backend — specific layer
./gradlew test --tests "com.peoplemanager.domain.*"
./gradlew test --tests "com.peoplemanager.application.*"
./gradlew test --tests "com.peoplemanager.adapters.web.*"
./gradlew test --tests "com.peoplemanager.integration.*"
# Frontend — unit + component tests
cd frontend && npm test
# Frontend — tests with coverage
npm run test:coverage
# Frontend — end-to-end tests (requires running stack)
npm run test:e2e
# dev.sh tests
./tests/test_dev_sh.shAll backend database tests use Testcontainers with real PostgreSQL — no H2.
CrewCaptain exposes a Prometheus-compatible metrics endpoint for monitoring.
| Endpoint | Auth | Description |
|---|---|---|
/actuator/health |
None | Health check (used by Docker healthcheck) |
/actuator/prometheus |
Bearer token (METRICS_TOKEN) |
Prometheus metrics scrape endpoint |
-
Set
METRICS_TOKENin your.envfile:METRICS_TOKEN=$(openssl rand -hex 32) -
Configure your Prometheus
scrape_configs:scrape_configs: - job_name: 'crewcaptain-api' metrics_path: '/actuator/prometheus' bearer_token: '<your-METRICS_TOKEN-value>' static_configs: - targets: ['api:8080']
| Metric | Type | Description |
|---|---|---|
crewcaptain_one_on_ones |
Gauge | Total number of 1:1 entries across all users |
crewcaptain_one_on_ones_last_7_days |
Gauge | 1:1 entries with meeting date in the last 7 days |
jvm_memory_* |
Various | JVM memory usage (heap, non-heap, buffers) |
jvm_gc_* |
Various | Garbage collection stats |
hikaricp_* |
Various | Database connection pool metrics |
http_server_requests_* |
Timer | HTTP request latency and count by endpoint |
application_ready_time_seconds |
Gauge | Application startup time |
All metrics include the tag application="crewcaptain".
- If
METRICS_TOKENis not configured, the/actuator/prometheusendpoint returns403 Forbidden. - All other actuator endpoints (except
/actuator/health) are blocked. - The metrics endpoint does not expose any user data — only operational metrics.
Schema changes are managed via Flyway. Current migrations:
| Migration | Description |
|---|---|
V20250508120000 |
Create users table |
V20250508120001 |
Create persons table |
V20250508120002 |
Create pinned_remember_items table |
V20250508120003 |
Create one_on_one_series table |
V20250508120004 |
Create one_on_one_entries table |
V20250508120005 |
Create agenda_items table |
V20250510120000 |
Create action_items table |
V20250510120001 |
Create pdp_goals table |
V20250510120002 |
Create pdp_updates table |
V20250510120003 |
Create kudos table |
V20250510120004 |
Create quick_notes table |
V20250510120005 |
Add attached_entry_id to quick_notes |
V20250510120006 |
Create notifications table |
V20250510120007 |
Add full-text search support (placeholder — no schema changes needed) |
V20250510120008 |
Create user_settings table |
V20250510120009 |
Add GIN indexes for full-text search (immutable wrapper functions + expression indexes) |
V20250510120010 |
Add soft-delete support to persons table (deleted_at column + indexes) |
V20250511120000 |
Create audit_log table with indexes |
V20250511120001 |
Cascade person delete to child tables (action_items, pdp_goals, kudos, quick_notes) |
V20250511120002 |
Create workspaces table |
V20250515120000 |
Add self_assigned flag to quick_notes |
V20250517120000 |
Add AI settings to user_settings |
V20250517130000 |
Add AI writing style to user_settings |
V20250518120000 |
Add custom AI prompts to user_settings |
V20250518130000 |
Add outcome extractor prompt to user_settings |
V20250519120000 |
Add trend radar prompt to user_settings |
V20250524120000 |
Create strategy_goals table |
V20250524120001 |
Create strategy_goal_pdp_goal_links table |
V20250524120002 |
Add GIN index for strategy_goals full-text search |
V20250525100000 |
Add link suggestions prompt to user_settings |
V20250525120000 |
Add strategy optimization prompt to user_settings |
V20250602120000 |
Add sticky-note fields to pinned_remember_items |
V20250606120000 |
Add snoozed_until to action_items |
V20250607120000 |
Add AI auto-execute flag and command terminal prompt to user_settings |
New migrations must follow the naming convention: V{timestamp}__{description}.sql
-
In your authentik admin panel, create a new OAuth2/OIDC Provider:
- Name:
crewcaptain - Client type: Confidential
- Redirect URIs:
http://localhost:3000/api/auth/callback/oidc - Signing key: Select or create an RSA key
- Scopes: Add the
offline_accessscope mapping (required for refresh tokens). If it doesn't exist, create a Property Mapping with scope nameoffline_accessand expressionreturn {}.
- Name:
-
Create an Application linked to the provider:
- Name:
CrewCaptain - Slug:
crewcaptain
- Name:
-
Note the following values for your
.env:OIDC_CLIENT_ID— from the providerOIDC_CLIENT_SECRET— from the providerOIDC_ISSUER_URI/OIDC_ISSUER—https://your-authentik/application/o/crewcaptain/OIDC_JWKS_URI—https://your-authentik/application/o/crewcaptain/jwks/
Note: Without the
offline_accessscope mapping, authentik will not issue refresh tokens and users will be forced to re-login when the access token expires (typically every 5 minutes).
# Using Docker Compose
docker compose exec db pg_dump -U crewcaptain crewcaptain > backup_$(date +%Y%m%d).sql
# Direct connection
pg_dump -h localhost -U crewcaptain -d crewcaptain > backup_$(date +%Y%m%d).sql# Using Docker Compose
cat backup_20250508.sql | docker compose exec -T db psql -U crewcaptain -d crewcaptain
# Direct connection
psql -h localhost -U crewcaptain -d crewcaptain < backup_20250508.sql/
├── dev.sh ← Local dev runner (./dev.sh backend | frontend)
├── docker-compose.yml ← Production-like stack
├── .env.example ← Environment variable template
│
├── api/ ← Kotlin Spring Boot backend
│ ├── src/main/kotlin/com/peoplemanager/
│ │ ├── domain/ ← Aggregates, Value Objects, Enums (pure Kotlin, no framework deps)
│ │ │ └── service/ ← Domain services (CsvParser, formatters — pure logic)
│ │ ├── application/ ← Use Cases, Commands, Queries
│ │ │ ├── port/
│ │ │ │ ├── input/ ← Input Ports (CommandPort + QueryPort interfaces)
│ │ │ │ └── output/ ← Output Ports (Repositories, AI, Encryption)
│ │ │ ├── commands/ ← Command objects (write DTOs)
│ │ │ └── queries/ ← Query objects (read DTOs)
│ │ └── adapters/
│ │ ├── web/ ← REST Controllers + DTOs
│ │ ├── persistence/ ← JPA Repositories + Entities
│ │ ├── ai/ ← LLM/AI client adapter
│ │ ├── auth/ ← OIDC/JWT verification + user provisioning
│ │ ├── encryption/ ← AES-GCM encryption adapter
│ │ ├── metrics/ ← Prometheus custom metrics
│ │ └── scheduler/ ← Notification generation (hourly scheduled)
│ ├── src/main/resources/db/migration/ ← Flyway migrations
│ ├── src/test/kotlin/ ← Tests (domain, application, web, integration, architecture)
│ ├── ARCHITECTURE.md ← Detailed architecture guide & migration playbook
│ ├── build.gradle.kts
│ └── Dockerfile
│
└── frontend/ ← Next.js frontend
├── src/
│ ├── app/ ← App Router pages (people list, detail, create)
│ ├── components/ ← UI components (PersonCard, FilterBar, MoraleIndicator, etc.)
│ ├── lib/ ← API client
│ └── types/ ← TypeScript type definitions
├── __tests__/ ← Jest + React Testing Library (components, lib, pages)
├── e2e/ ← Playwright end-to-end tests (planned)
├── package.json
└── Dockerfile
The project ships pipelines for both hosting setups, so the same checks run wherever the code lives:
- GitLab —
.gitlab-ci.yml(used by the maintainer's self-hosted GitLab instance). - GitHub —
.github/workflows/ci.yml(GitHub Actions).
Both run the identical logical stages.
| Stage | Trigger | Jobs |
|---|---|---|
| test | All branches / PRs | test-api (Gradle + Testcontainers), test-frontend (Jest) |
| build | main only |
build-api (Docker image), build-frontend (Docker image) |
Docker images are published on every push to main:
- GitLab CI → the GitLab Container Registry (
<registry>/apiand<registry>/frontend). - GitHub Actions → the GitHub Container Registry (
ghcr.io/<owner>/<repo>/apiand.../frontend).
Both tag images with latest and the commit SHA.
- GitLab: runners must support Docker-in-Docker (
docker:24-dindservice), used for both Testcontainers (backend tests) and image builds. - GitHub: the default
ubuntu-latestrunners ship with Docker preinstalled, so Testcontainers and image builds work with no extra services.
CrewCaptain is privacy-first. Next.js telemetry is disabled in the Docker image via the NEXT_TELEMETRY_DISABLED=1 environment variable, set in both the build and runtime stages of the frontend Dockerfile. No anonymous usage data is sent to Vercel during builds or at runtime.
For local development outside Docker, you can disable telemetry manually:
npx next telemetry disableOr set the environment variable in your shell:
export NEXT_TELEMETRY_DISABLED=1See Next.js Telemetry for details on what would be collected if enabled.
- Read
AGENTS.mdfor the full development workflow and architecture rules - Read
api/ARCHITECTURE.mdfor hexagonal architecture guide and migration playbook - Follow the mandatory workflow: Branch → Read → Plan → Test → Code → Verify → Docs → Log → Commit
- Every change must include tests — no exceptions
- All data queries must be scoped by
userId(security invariant) - Architecture boundaries are enforced by ArchUnit tests — the build will fail if violated
- Update
README.mdandPROGRESS.mdwith every change - Use conventional commits (see
AGENTS.md§4.3)
This project is licensed under the GNU Affero General Public License v3.0.