# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What This Is

**Canvas Notebook** is a self-hosted Next.js web app combining:
- A file browser + code editor (CodeMirror) for a workspace directory
- A terminal emulator (xterm.js + node-pty via WebSocket)
- A spreadsheet viewer (UniverseJS)
- An AI agent chat interface powered by the **PI framework** (`@mariozechner/pi-ai` + `@mariozechner/pi-agent-core`)

## Commands

```bash
npm run dev                     # Start dev server (tsx server.js, not next dev)
npm run build                   # Production build (next build --webpack)
npm run start                   # Production start (bootstrap admin + tsx server.js)
npm run lint                    # ESLint

npm run bootstrap:admin         # Create initial admin user from BOOTSTRAP_ADMIN_* env vars

npm run test:smoke              # Quick smoke test
npm run test:integration        # API integration tests
npm run test:integration:pi     # PI agent integration tests
npm run test:pi:attachments     # PI attachment tests
npm run test:prompt-builder     # Prompt builder tests
npm run test:e2e                # Playwright E2E tests
npm run test:all                # Full suite (build + all above)
```

**Important:** Every `dev`, `build`, `lint`, and `test:*` command runs `npm run todos:sync:agent` as a pre-hook, which syncs implementation TODOs from managed files in `app/lib/agents/`.

The dev server is `tsx server.js` (not `next dev`) because the custom server adds WebSocket support for terminals and authenticated `/media/` file serving.

## Architecture

### Custom Server (`server.js`)
The app runs through a custom Node.js server that wraps Next.js:
- Handles `/media/` routes with auth-gated static file serving (range requests, cache control)
- Attaches a WebSocket server for terminal sessions (`server/terminal-server.js`)
- Delegates everything else to Next.js

### PI Agent Streaming (`/ws/chat`)
The main AI agent transport is the authenticated chat WebSocket at `/ws/chat`. It:
1. Accepts request/response messages for `subscribe_session`, `send_message`, `control`, and `get_status`
2. Resolves provider/model from config via `app/lib/pi/model-resolver.ts`
3. Resolves the API key via `app/lib/pi/api-key-resolver.ts`
4. Looks up or creates a PI session in SQLite (`pi_sessions`, `pi_messages` tables)
5. Runs the PI agent loop with registered tools from `app/lib/pi/tool-registry.ts`
6. Streams runtime events back as WebSocket `agent_event` messages

Tools available to the agent: `ls`, `read`, `write`, `mkdir`, `terminal exec`, and others registered in `tool-registry.ts`.

### Database (`app/lib/db/`)
SQLite via Drizzle ORM. Schema in `app/lib/db/schema.ts`. Migration config in `drizzle.config.ts`.

**Migrations** are managed in `app/lib/db/migrate.ts` and run automatically at server startup (before `app.prepare()`) via `server.js`. `db/index.ts` also calls `runMigrations()` on module load as a fallback (e.g. for tests and scripts).

**To add a new table:** add `CREATE TABLE IF NOT EXISTS` to `migrate.ts`.
**To add a new column to an existing table:** add an `addColumns(sqlite, 'table_name', { col: 'TYPE DEFAULT ...' })` entry in the column-additions section of `migrate.ts`. Do NOT add migrations to `db/index.ts` — that file is now just a thin wrapper.

The `drizzle/` folder contains a reference migration generated by drizzle-kit but it is **not applied automatically** — the custom `migrate.ts` runner is used instead.

Tables:
- `user`, `session`, `account`, `verification` — managed by better-auth
- `pi_sessions`, `pi_messages` — PI agent conversation persistence
- `ai_sessions`, `ai_messages` — legacy, kept for compatibility
- `studio_generations`, `studio_generation_outputs`, `studio_products`, `studio_personas`, `studio_styles`, `studio_presets`, `studio_bulk_jobs`, `studio_bulk_job_line_items` — Studio image/video generation
- `automation_jobs`, `automation_runs` — scheduled automations
- `pi_usage_events` — token usage tracking
- `oauth_tokens` — provider OAuth tokens
- `user_hint_state`, `page_onboarding_state`, `onboarding_log` — onboarding state

### Authentication (`app/lib/auth.ts`)
better-auth with SQLite adapter. Session cookies cached for 5 minutes. Signup can be disabled via `ALLOW_SIGNUP=false`. Custom `role` field on users. Bootstrap admin user created from `BOOTSTRAP_ADMIN_EMAIL` / `BOOTSTRAP_ADMIN_PASSWORD` env vars.

### Terminal (`server/terminal-server.js`, `server/terminal-manager.js`)
WebSocket-based PTY sessions. Max 3 terminals per user, 30-min idle timeout. Sessions are in-memory only (lost on server restart).

### Workspace & Files
Base data path is configurable via `DATA` env (defaults to `./data`). Workspace files are stored under `${DATA}/workspace`. File CRUD via `app/api/files/`. The workspace path is sandboxed — file API rejects paths that escape it.

### PI Agent Config (`app/lib/pi/config.ts`)
Config is file-based (PI v2 format), provider-driven. Supports openrouter, anthropic, google, ollama. The agent system prompt is composed from managed files in `app/lib/agents/` (synced by `scripts/sync-agent-todos.mjs`).

## Key Environment Variables

```
BETTER_AUTH_SECRET          # Required: 32-byte base64 session key
BETTER_AUTH_BASE_URL        # Required: auth redirect base URL
DATA                        # Base data path (default: ./data). Workspace, SQLite, and skills stored under this path.
ALLOW_SIGNUP                # Set to "false" to disable registration
BOOTSTRAP_ADMIN_EMAIL       # Admin email for initial setup
BOOTSTRAP_ADMIN_PASSWORD    # Admin password for initial setup
CLAUDE_API_KEY              # Anthropic Claude API key
OPENROUTER_API_KEY          # OpenRouter API key
GEMINI_API_KEY              # Google Gemini API key
OLLAMA_BASE_URL             # Local Ollama endpoint
INTEGRATIONS_ENV_PATH       # Path to integrations secrets file
AGENTS_ENV_PATH             # Path to agents secrets file
```

## State Management

UI state is managed with Zustand. The main store is in `app/lib/store/` or co-located with components. Server state flows through Next.js API routes — no tRPC or React Query.

## Docker

```bash
docker compose up   # Uses compose.yaml, single service
```

Data volumes: `/data/workspace` (files), `/data/sqlite.db` (database), `/home/node` (npm globals/CLI config). Optionally installs `codex`, `claude`, or `ollama` at container startup based on env vars.
