This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
This project uses pnpm exclusively. Always use pnpm / pnpm dlx instead of npm / npx.
Lumo is a local-first desktop application for monitoring Claude Code usage. It collects telemetry data via OpenTelemetry Protocol (OTLP) and hook events, then displays usage analytics in a native desktop UI.
Key characteristics:
- No accounts, no cloud services, no data leaves your machine
- All data stored locally in SQLite at
~/.lumo/ - Daemon auto-installed and managed by the desktop app on macOS
- Daemon (
crates/daemon/): Standalone HTTP service (Axum) that receives OTLP metrics/logs and hook notifications from Claude Code - Shared Library (
crates/shared/): Common database layer (entities, repositories, migrations) used by both daemon and Tauri app - Tauri App (
src-tauri/): Desktop application shell with native OS integration, IPC commands, and business services - Frontend (
packages/ui/): React-based UI (pnpm workspace package@lumo/ui)
Each module has its own CLAUDE.md with detailed patterns and guidelines:
crates/daemon/CLAUDE.md- Daemon service architecturecrates/shared/CLAUDE.md- Database layer and entity patternssrc-tauri/CLAUDE.md- Tauri backend, services, and IPC commandspackages/ui/CLAUDE.md- Frontend architecture and component patterns
Claude Code
|
|-- OTLP logs/metrics (HTTP JSON) --> Lumo Daemon (port 4318) --> SQLite (~/.lumo/lumo.db)
|-- Hook events (/notify endpoint) ----^ |
v
Tauri App (reads DB)
|
v
React UI (Next.js SSG)
- Claude Code emits OTLP telemetry and hook events
- Lumo Daemon receives them via HTTP endpoints and persists to SQLite
- Tauri App reads from the same SQLite database and serves data to the frontend via IPC
- React UI displays dashboards, session history, tool analytics, and usage insights
The Tauri app automatically:
- Initializes the SQLite database and runs migrations
- Ensures the daemon is installed and running (macOS:
launchdagent) - Configures
~/.claude/settings.jsonfor OTLP export and hooks - Starts background session file watcher and notification poller
-
Daemon (
crates/daemon/): OTLP telemetry receiver- Standalone HTTP service using Axum
- Endpoints:
/health,/v1/metrics,/v1/logs,/notify - Persists data to SQLite via shared library
- Runs independently of Tauri app
-
Shared Library (
crates/shared/): Common database layer- Entity definitions with Row/Domain type split
- Repository pattern for data access (events, metrics, sessions, notifications)
- Migrations and connection management
- Used by both daemon and Tauri app
-
Tauri Backend (
src-tauri/): Desktop application shell- Commands (
commands/): IPC handlers for frontend communication - Services (
services/): Business logic (analytics, trends, tools, usage, wrapped, etc.) - Types (
types/): Response types with#[typeshare]for TypeScript generation - Daemon (
daemon/): Daemon lifecycle management (install, health check, launchd plist) - Plugins: log, clipboard, dialog, updater, process, notification, window-state
- Commands (
UI (packages/ui/): Next.js 16 with App Router (SSG mode)
- Pages: Overview, Sessions, Session Detail, Tools, Analytics, Usage, Wrapped
- TanStack Query + Tauri IPC for data fetching
- Tailwind CSS v4 + shadcn/ui components
- ECharts for charts and visualizations
Migration (SQL) --> Entity (Row + Domain) --> Repository --> Service --> Command --> Bridge --> useService hook --> UI
- Migrations (
crates/shared/migrations/): SQL schema versioning - Entities (
crates/shared/src/database/entities/):*Row(FromRow) + Domain (Serialize) +New*(insertion) - Repositories (
crates/shared/src/database/repositories/): Static methods with&SqlitePool - Services (
src-tauri/src/services/): Business logic, aggregation, calculations - Commands (
src-tauri/src/commands/):#[command]IPC handlers - Types (
src-tauri/src/types/): Response types with#[typeshare]annotation - Bridges (
packages/ui/src/bridges/): TypeScript classes wrappinginvoke()calls - Hooks (
packages/ui/src/modules/*/use-service.ts): TanStack Query integration - UI (
packages/ui/src/modules/*/index.tsx): Pure rendering components
- Node.js >= 24.12
- pnpm >= 10.26
- Rust (stable, >= 1.77.2)
- Platform dependencies for Tauri v2
pnpm tauri:dev # Start Tauri app with frontend dev server (recommended)
pnpm tauri build # Build production Tauri application bundlepnpm dev # Start Next.js dev server on localhost:3000
pnpm build # Build Next.js static export to packages/ui/out/
pnpm lint # Run Biome linter
pnpm format # Format code with Biome
pnpm check # Lint + format with Biome (auto-fix)cargo run -p lumo-daemon # Run daemon in development
RUST_LOG=debug cargo run -p lumo-daemon # Run with debug loggingcargo check # Check all crates
cargo check -p app # Check Tauri app (note: package name is "app", not "lumo-app")
cargo check -p lumo-daemon # Check daemon
cargo check -p shared # Check shared librarypnpm generate-types # Generate TypeScript types from Rust #[typeshare] structsNote: pnpm tauri:dev automatically runs type generation before starting.
- Frontend Build: Next.js builds a static site to
packages/ui/out/ - Daemon Build:
cargo build -p lumo-daemon --release(triggered bybeforeBuildCommand) - Tauri Bundle: Packages static frontend + daemon binary + Rust backend into native executable
Key configuration:
packages/ui/next.config.ts:output: 'export'for SSGsrc-tauri/tauri.conf.json:frontendDistpoints to../packages/ui/outsrc-tauri/tauri.conf.json:resourcesbundles the daemon binary
lumo/
├── packages/
│ └── ui/ # Next.js frontend (@lumo/ui)
│ ├── app/ # Next.js App Router (route definitions only)
│ │ ├── page.tsx # Overview
│ │ ├── sessions/ # Sessions list + detail
│ │ ├── tools/ # Tool analytics
│ │ ├── analytics/ # Performance analytics
│ │ ├── usage/ # Subscription usage
│ │ └── wrapped/ # Claude Code Wrapped
│ └── src/
│ ├── bridges/ # Tauri IPC wrappers
│ ├── components/ # Shared React components
│ │ └── ui/ # shadcn/ui (READ-ONLY)
│ ├── generated/ # Auto-generated TypeScript types
│ ├── hooks/ # Global React hooks
│ ├── lib/ # Utilities (query-client, format, utils)
│ └── modules/ # Page-level business logic
│ ├── overview/ # / route
│ ├── sessions/ # /sessions route
│ ├── session-detail/ # /sessions/detail route
│ ├── tools/ # /tools route
│ ├── analytics/ # /analytics route (not yet listed)
│ ├── usage/ # /usage route
│ └── wrapped/ # /wrapped route
├── crates/
│ ├── daemon/ # OTLP receiver service
│ │ └── src/
│ │ ├── handlers/ # Request handlers
│ │ ├── routes/ # Route definitions
│ │ ├── services/ # OTLP parsing
│ │ └── server/ # App state, shutdown
│ └── shared/ # Shared database library
│ ├── src/database/
│ │ ├── entities/ # event, metric, session, notification
│ │ └── repositories/ # event_repo, metric_repo, session_repo, notification_repo
│ └── migrations/ # SQL migration files
├── src-tauri/ # Tauri desktop app
│ └── src/
│ ├── commands/ # IPC handlers
│ ├── services/ # Business logic
│ ├── types/ # Response types with #[typeshare]
│ ├── daemon/ # Daemon lifecycle management
│ ├── database/ # DB setup (delegates to shared)
│ └── lib.rs # App setup, plugin registration
├── scripts/ # Build & install scripts
├── turbo.json # Turborepo config
├── pnpm-workspace.yaml # pnpm workspace config
├── Cargo.toml # Cargo workspace config
└── package.json # Root scripts
Create SQL file in crates/shared/migrations/ with timestamp prefix:
-- crates/shared/migrations/YYYYMMDDHHMMSS_description.sql
CREATE TABLE IF NOT EXISTS my_table (...);- Define
MyEntityRow(FromRow),MyEntity(Serialize),NewMyEntityincrates/shared/src/database/entities/ - Create repository with static async methods in
crates/shared/src/database/repositories/ - Export in respective
mod.rsfiles
- Define response types in
src-tauri/src/types/with#[typeshare]annotation - Note:
typesharedoes NOT supporti64- usef64for large numbers
- Add business logic in
src-tauri/src/services/ - Register in
src-tauri/src/services/mod.rs
- Add
#[command]handlers insrc-tauri/src/commands/ - Register in
app_commands!macro insrc-tauri/src/commands/mod.rs
pnpm generate-types- Create
packages/ui/src/bridges/my-bridge.tswrappinginvoke()calls
- Create module in
packages/ui/src/modules/my-module/index.tsx- UI rendering onlyuse-service.ts- TanStack Query hooks + statetypes.ts- TypeScript types
- Create route in
packages/ui/app/my-route/page.tsx - Add sidebar entry in
packages/ui/src/components/app-sidebar/constants.ts
| Area | Convention |
|---|---|
| Package manager | pnpm only |
| Rust package names | app (Tauri), lumo-daemon, shared |
| Entity pattern | Row (FromRow) -> Domain (Serialize) -> New (insert) |
| Repository pattern | Static async methods taking &SqlitePool |
| Command pattern | #[command] pub async fn returning Result<T, String> |
| Typeshare location | src-tauri/src/types/ (NOT in shared crate) |
| Frontend components | Must be folders with index.tsx + use-service.ts |
| UI/logic separation | index.tsx = JSX only, use-service.ts = all hooks/state |
| Styling | Tailwind CSS only, theme tokens (no hardcoded grays) |
| shadcn/ui | components/ui/ is READ-ONLY |
| Icons | lucide-react with size-4 convention |
See individual module CLAUDE.md files for detailed patterns.