CLAUDE.md · git:20260503.46dcc1b · 2026-05-03 · sha256 f3d0087fb647fb78
CLAUDE.md git:20260503.46dcc1bA
Immutable. This exact content never changes and is served at /api/v1/blob/f3d0087fb647fb78.
# 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.