* chore: migrate cursor rules to portable agent skills and add mantine MCP guidance Replace Cursor-only .cursor/rules/*.mdc with Agent Skills open-standard SKILL.md files in .agents/skills/ (canonical), mirrored to .cursor/skills/ and .claude/skills/ so they work across Claude Code, Cursor, Codex, and OpenCode. Add mantine MCP server guidance to AGENTS.md and un-ignore .agents/skills/ in .gitignore. * refactor: single-source agent skills at .agents/skills Cursor, Codex, and OpenCode all read .agents/skills/ natively, so the .cursor/skills/ mirror was redundant. Claude Code reads .claude/skills/, which is now a symlink back to .agents/skills/ so the content lives once. Deletes .cursor/skills/ and converts .claude/skills/ to a symlink. * chore: ship mantine MCP server via project-scoped .mcp.json Declares the keyless @mantine/mcp-server in .mcp.json (Claude Code project-scope standard, committable) and documents MCP server setup and the mantine entry in AGENTS.md. * chore: address coderabbit security-review and markdown lint findings Add explicit security-review guidance before exposing MCP tools and label plain-text code fences to satisfy markdownlint MD040.
10 KiB
name, description
| name | description |
|---|---|
| codebase-context | Comprehensive Homarr codebase architecture and conventions reference. Use when orienting in the monorepo, understanding package dependency layers, the three runtime services and ports, tRPC router structure, multi-database support, widget/integration/cron system architecture, routing, auth providers, env vars, or key patterns before writing code. |
Homarr Codebase Context
Homarr is an open-source, self-hosted dashboard for managing homelab services. It integrates with 50+ self-hosted apps (media servers, download clients, DNS, NAS, etc.) and provides a drag-and-drop widget-based UI.
Tech Stack
- Monorepo: pnpm workspaces + Turborepo
- Framework: Next.js (App Router,
output: "standalone") — T3 Stack - Language: TypeScript throughout
- ORM: Drizzle (supports SQLite via better-sqlite3, MySQL, PostgreSQL)
- API: tRPC (HTTP + WebSocket subscriptions) with superjson + OpenAPI bridge via
trpc-to-openapi - Auth: NextAuth v5 (database sessions, Credentials/LDAP/OIDC providers)
- UI: Mantine v9 (not Tailwind) + Tabler icons
- State: Jotai (atoms), TanStack Query (server state via tRPC)
- Realtime: WebSocket server (
ws) for tRPC subscriptions, backed by Redis pub/sub - Cron:
node-cronin a standalone Fastify service (apps/tasks) - i18n: next-intl with
[locale]dynamic segment (prefix mode:"never", locale from cookie) - Testing: Vitest + jsdom, Playwright for E2E
- Lint/Format: oxlint + oxfmt (not ESLint/Prettier)
- Docs: Docusaurus 3 in
apps/docs/(@homarr/docs) - Package manager: pnpm 10.34.1, Node >= 24.16.0
Repository Structure
homarr/
├── apps/
│ ├── nextjs/ # Main Next.js application (port 3000)
│ ├── docs/ # Docusaurus 3 documentation site (@homarr/docs)
│ ├── tasks/ # Cron job runner + Fastify tRPC API (port 3002)
│ └── websocket/ # Standalone tRPC WebSocket server (port 3001)
├── packages/
│ ├── api/ # tRPC appRouter, procedures, OpenAPI
│ ├── auth/ # NextAuth config, providers, session, API keys
│ ├── db/ # Drizzle schema (3 DB drivers), migrations, queries
│ ├── core/ # Env validation, DB/Redis driver factories, logging
│ ├── definitions/ # Domain enums: WidgetKind, IntegrationKind, permissions
│ ├── widgets/ # All 39 dashboard widgets (definitions + components)
│ ├── integrations/ # Integration classes (HTTP clients to external apps)
│ ├── redis/ # Redis pub/sub channels, caching abstractions
│ ├── translation/ # next-intl setup, locale configs, lang JSON files
│ ├── ui/ # Shared Mantine components, theme, hooks
│ ├── validation/ # Shared zod schemas for API/forms
│ ├── common/ # Shared utilities, IDs, errors
│ ├── cron-jobs/ # Cron job implementations (25+ jobs)
│ ├── cron-jobs-core/ # Cron scheduling primitives
│ ├── cron-job-api/ # tRPC router for job management (start/stop/trigger)
│ ├── cron-job-status/ # Cron status via Redis
│ ├── boards/ # Board context, edit mode, cache updater
│ ├── modals/ # Modal primitives on Mantine
│ ├── modals-collection/ # Feature modals (apps, boards, docker, etc.)
│ ├── form/ # useZodForm (Mantine + zod resolver)
│ ├── forms-collection/# Reusable form UIs (new app, icon picker, upload)
│ ├── spotlight/ # Command palette / search with multiple modes
│ ├── request-handler/ # Server request handlers (feeds, integrations)
│ ├── notifications/ # Mantine notifications wrapper
│ ├── docker/ # Dockerode-based Docker access
│ ├── icons/ # Icon DB/repo integration
│ ├── image-proxy/ # Image proxy + caching
│ ├── ping/ # Reachability / ping utilities
│ ├── analytics/ # Server-side analytics (Umami)
│ ├── server-settings/ # Server setting keys/types
│ ├── settings/ # User-facing settings UI context
│ └── cli/ # Node CLI for ops (brocli)
├── tooling/
│ ├── typescript/ # Base tsconfig
│ └── github/ # CI setup action
├── development/ # Dev docker-compose (Redis, MySQL, PostgreSQL)
├── e2e/ # E2E test specs
└── Dockerfile # Multi-stage production build
Three Runtime Services
| Service | Port | Role |
|---|---|---|
| Next.js | 3000 | Main web app (SSR + API routes) |
| WebSocket | 3001 | tRPC subscriptions via ws |
| Tasks | 3002 | Cron job runner + management API via Fastify |
In Docker, nginx listens on 7575 and proxies to all three. The tasks service authenticates via CRON_JOB_API_KEY header.
Package Dependency Layers
- Foundation:
core(env, DB drivers, Redis client, logging) → no@homarrdeps - Domain:
definitions→common→core - Data:
db→core,definitions,common - Auth:
auth→db,definitions,core,validation - Cross-cutting:
translation,validation,redis,server-settings - Feature backends:
integrations,request-handler,cron-jobs,docker,ping - API surface:
api→ pulls in auth, db, redis, integrations, etc. - UI stack:
ui,modals,form,notifications,boards,spotlight - Composed features:
widgets,modals-collection,forms-collection
Key Patterns
tRPC
- Server caller (RSC):
import { api } from "@homarr/api/server"→ cached caller with auth context - Client hooks:
import { clientApi } from "@homarr/api/client"→ React Query hooks - Router structure:
packages/api/src/root.tsdefines top-level namespaces (user,board,widget,integration,docker,kubernetes, etc.) - Procedures:
publicProcedure,protectedProcedure,permissionRequiredProcedure,onboardingProcedure - WebSocket subscriptions: Client uses
wsLinkwhentype === "subscription", connects to port 3001
Multi-Database Support
Drizzle schemas exist in three parallel implementations:
packages/db/schema/sqlite.tspackages/db/schema/mysql.tspackages/db/schema/postgresql.ts
Selected at runtime via DB_DRIVER env (better-sqlite3 | mysql2 | node-postgres). Migrations are per-engine in packages/db/migrations/{sqlite,mysql,postgresql}/.
Widget System
Widgets are registered in packages/widgets/src/index.tsx as widgetImports (must satisfy Record<WidgetKind, ...>). Each widget:
- Has a
definitioncreated viacreateWidgetDefinition(kind, { icon, createOptions, supportedIntegrations? }) - Uses
optionsBuilder.from(factory => ({ ... }))for typed options - Loads dynamically via
next/dynamic - Fetches data via tRPC client hooks (backed by cron job caches)
Integration System
Integrations are defined in packages/definitions/src/integration.ts (integrationDefs) and instantiated via createIntegrationAsync in packages/integrations/src/base/creator.ts. Each extends abstract Integration class with testConnectionAsync and typed methods.
Cron Job System
Jobs defined in packages/cron-jobs/src/jobs/. Each job:
- Uses
createCronJobwith a cron expression - Publishes results to Redis channels
- Widget subscriptions consume those channels via tRPC subscriptions
- Job management (start/stop/trigger) via
cronJobApiHTTP client to port 3002
Modals
Created with createModal(component).withOptions({ defaultTitle }) from @homarr/modals. Opened via useModalAction(SomeModal). Feature modals live in packages/modals-collection/.
Forms
useZodForm from @homarr/form wraps Mantine useForm with zod validation. Shared form UIs in packages/forms-collection/.
Routing (App Router)
apps/nextjs/src/app/[locale]/— all UI routes under locale segment- Route groups:
(home),(content),(board)— don't affect URLs - Key routes:
boards/[name],manage/...(admin),auth/login,init(onboarding),widgets/[kind] - Locale driven by cookie (no URL prefix)
Auth Providers
Configured via AUTH_PROVIDERS env. Supports: credentials (local), ldap, oidc. Database sessions with httpOnly cookies. API key auth for programmatic access.
Environment Variables
Key env vars (from .env.example):
DB_DRIVER:better-sqlite3|mysql2|node-postgresDB_URL: Database connection stringAUTH_SECRET: NextAuth secretSECRET_ENCRYPTION_KEY: AES-256-CBC for integration secretsAUTH_PROVIDERS: Comma-separated (credentials,ldap,oidc)CRON_JOB_API_KEY: Shared key between Next.js and tasks serviceREDIS_*: Redis connection (host, port, password)ENABLE_KUBERNETES: Optional Kubernetes integration
Development Commands
pnpm dev— runs all three services in parallel via Turborepopnpm docker:dev— starts Redis + MySQL + PostgreSQL containerspnpm db:push— push schema to SQLite (dev)pnpm db:studio— Drizzle Studiopnpm test— Vitest unit testspnpm test:e2e— Playwright E2Epnpm lint/pnpm format— oxlint + oxfmtpnpm dev:docs— Docusaurus docs site (port 3003)pnpm typecheck— tsc --noEmit across all packages
Conventions
- Workspace packages use
@homarr/scope - All versions managed via pnpm
catalog:in workspace - Path alias
~/*→./src/*in Next.js app - Mantine for all UI (no Tailwind) — primary color is red,
autoContrast: true - Icons from
@tabler/icons-react - Drag-and-drop via
@dnd-kit/* typescript.ignoreBuildErrors: truein next.config (types checked separately viatypecheck)serverExternalPackages:dockerode,isomorphic-dompurify,jsdom