CLAUDE.md
This file provides guidance to Claude Code when working with the Website CMS.
Project Overview
Website CMS is an AI-native content management system that combines:
- WordPress: Anyone can create dynamic websites
- PocketBase: Flexible data management (collections, documents, ACL)
- AI-First: Conversation-driven site building and content editing
Single-site solution (like WordPress/PocketBase) - no multi-tenancy complexity.
Target Audience
Anyone who wants to build and ship software. With AI making creation accessible to everyone, the remaining barrier is DevOps — ReadySite removes it. Current users should be comfortable with HTML/CSS and API key configuration, but the goal is to make this accessible to non-technical users too (WYSIWYG editor on roadmap).
Key Decisions
- AI Configuration: OAuth for OpenAI/Anthropic is NOT viable (neither offers it). Continue with API key approach.
- Versioning: Replacing boolean
Publishedwith version-based system (draft/published per PageContent). - Visual Editor: Adding WYSIWYG "Editor" tab using Tiptap/React alongside existing Code tab.
- Terminology: Keep "Collections" (PocketBase-inspired), "URL Slug" as-is.
Architecture
website/
├── CLAUDE.md # This file
├── main.go # Application entry point
├── models/ # Pure data types + relationship methods
│ ├── db.go # Database setup + collection variables
│ ├── user.go # User
│ ├── page.go # Page (HTML content)
│ ├── collection.go # Collection (dynamic schemas)
│ ├── document.go # Document (collection records)
│ ├── conversation.go # Conversation (AI chat)
│ ├── message.go # Message
│ ├── mutation.go # Mutation (undo support)
│ └── acl.go # ACLRule
├── internal/ # Business logic (imports models)
│ ├── access/ # ACL, JWT tokens, password hashing, rate limiting
│ ├── api/ # API filter parsing + OpenAPI documentation
│ ├── assist/ # AI helpers, tool calls, tool definitions + executor
│ ├── audit/ # Audit logging
│ ├── content/ # Consolidated: page tree, path finder, slug validation, events,
│ │ # template functions, rule expressions, schema parsing/validation
│ ├── helpers/ # Utilities (pagination, etc.)
│ ├── seed/ # Database seeding
│ └── views/ # View collection query execution
├── controllers/
└── views/
Package Roles
| Package | Role | Imports |
|---|---|---|
models |
Pure data types, relationship traversal | pkg/database only |
internal/* |
Constants, business logic, JSON parsing | models |
controllers |
HTTP handlers, queries | models, internal/* |
Key rule: Models never import internal packages. Internal packages import models.
Models API
Types only have instance methods for relationship traversal:
// Page traversal
page.Parent() // *Page - parent page
page.Children() // []*Page - child pages
page.Siblings() // []*Page - same-level pages
page.Path() // string - full URL path
// Collection → Documents
collection.Documents() // []*Document
collection.DocumentCount() // int
// Conversation → Messages → Mutations
conversation.User() // *User
conversation.Messages() // []*Message
conversation.LastMessage() // *Message
message.Conversation() // *Conversation
message.Mutations() // []*Mutation
mutation.Message() // *Message
// Document data access (for templates)
doc.GetString("title")
doc.GetInt("price")
doc.GetBool("published")
doc.GetTime("createdAt")
doc.GetStrings("tags")
doc.Set("field", value)
doc.SetAll(data)
Data Model
| Model | Fields | Purpose |
|---|---|---|
| User | Email, PasswordHash, Name, Role, Verified | Authentication |
| Page | ParentID, Title, Description, HTML, Position, Published | Website pages |
| Collection | Name, Schema, Rules, System | Dynamic data schemas |
| Document | CollectionID, Data | Collection records |
| Conversation | UserID, Title, Context | AI chat sessions |
| Message | ConversationID, Role, Content, ToolCalls | Chat messages |
| Mutation | MessageID, Action, EntityType, Before/AfterState | Undo support |
| ACLRule | SubjectType, SubjectID, ResourceType, Permission | Access control |
Internal Packages
internal/access
// Constants
access.RoleAdmin, access.RoleUser, access.RoleViewer
access.SubjectUser, access.SubjectRole, access.SubjectPublic
access.PermRead, access.PermWrite, access.PermDelete, access.PermAdmin
access.JWTExpiration // 30 days
// ACL Functions
access.IsAdmin(user) bool
access.Grants(have, want string) bool
access.CheckAccess(user, resourceType, resourceID, permission) bool
// Authentication Functions (JWT, sessions, passwords)
access.SetSessionCookie(w, r, userID) error
access.ClearSessionCookie(w, r)
access.GetUserFromJWT(r) *User
access.HashPassword(password) (string, error)
access.CheckPassword(password, hash) bool
// Auth Collection Functions (API token auth)
access.CreateAuthToken(recordID, collectionID, tokenKey) (string, error)
access.ParseAuthToken(tokenString) (recordID, collectionID, tokenKey, error)
access.GetAuthRecordFromRequest(r) (recordID, collectionID)
access.GenerateTokenKey() string
// Rate Limiting
access.AuthLimiter, access.UploadLimiter, access.ChatLimiter, access.APILimiter
access.RateLimitAuth(handler) http.Handler
access.RateLimitUpload(handler) http.Handler
access.RateLimitChat(handler) http.Handler
access.ResourceLimiter // Configurable per-collection rate limiter
access.ParseRateLimitConfig(json) *RateLimitConfig
access.SetRateLimitHeaders(w, result)
internal/content
// Schema field types
content.Text, content.Number, content.Bool, content.Date, ...
// Schema types
content.Field{Name, Type, Required, Unique, Options}
// Schema functions
content.GetFields(collection) ([]Field, error)
content.SetFields(collection, fields) error
content.GetField(collection, name) (*Field, error)
// Schema validation
content.ValidateDocument(collection, data) error
content.ValidateDocumentJSON(collection, jsonStr) error
// Template functions for pages
content.Documents(collectionID, filter...) // Get documents
content.Document(id) // Get single document
content.Collection(id) // Get collection
content.Page(id) // Get page
content.Pages(parentID...) // Get pages
content.PublishedPages(parentID...) // Get published pages
content.Partial(id) // Get partial
content.Partials() // Get all partials
content.SiteName() // Site name setting
content.SiteDescription() // Site description
content.AllFuncs() // All template functions
// Rule expression evaluation
content.RuleContext{User, Record, OldRecord, Request}
content.EvaluateRule(expr, ctx) (bool, error)
// Page tree building
content.TreeNode{Page, Children}
content.BuildTree() []*TreeNode
// Page path lookup
content.FindByPath(path) *Page
// Slug validation
content.ValidateSlug(slug) error
content.CheckSlugAvailable(slug, resourceType) error
// Collection events (real-time pub/sub)
content.CollectionEvents // Global event bus
content.EventCreate, content.EventUpdate, content.EventDelete
bus.Subscribe(collectionID) *Subscriber
bus.Unsubscribe(sub)
bus.PublishCreate(collectionID, recordID, record)
bus.PublishUpdate(collectionID, recordID, record)
bus.PublishDelete(collectionID, recordID)
internal/assist
// Constants
assist.RoleUser, assist.RoleAssistant, assist.RoleSystem
assist.ActionCreate, assist.ActionUpdate, assist.ActionDelete
// Types
assist.ToolCall{ID, Name, Arguments, Result, Error}
assist.ConversationContext{CurrentPageID, ...}
// Functions
assist.GetToolCalls(message) ([]ToolCall, error)
assist.SetToolCalls(message, calls) error
assist.GetContext(conversation) (*ConversationContext, error)
assist.SetContext(conversation, ctx) error
assist.CanUndo(mutation), assist.CanRedo(mutation) bool
// Tool definitions and executor
assist.Executor - executes AI tool calls
assist.NewExecutor(ctx, userID, conversationID) *Executor
executor.Execute(toolCalls) []ToolResult
assist.ToolDefinitions() []openai.Tool // Available tools for AI
ID as Slug
All models use ID as both primary key and URL slug:
page := &models.Page{Title: "About"}
page.ID = "about" // Set before insert
models.Pages.Insert(page)
// Access via ID
page, _ := models.Pages.Get("about")
page.Path() // "/about"
Commands
go run ./website # Run (in-memory database)
DB_PATH=/data/website.db go run ./website # Run with local file database
go build ./website # Build
go test ./website/... # Test
go doc ./website/models # View API
Environment Variables
| Variable | Description |
|---|---|
DB_PATH |
Local SQLite file path (e.g., /data/website.db) |
DB_URL |
Remote libSQL server URL (e.g., libsql://mydb.turso.io) |
DB_TOKEN |
Authentication token for remote libSQL server |
AUTH_SECRET |
JWT signing secret (required for authentication) |
ENV |
Set to production to disable development features |
Database Selection: Uses engines.NewAuto() which selects based on environment:
DB_URL+DB_TOKEN→ Remote replica (syncs with Turso/libSQL primary)DB_PATHonly → Local file database- Neither → In-memory database (data lost on restart)
Known Limitations
Template Functions Don't Have Request Access
Template functions in internal/content/ don't receive *http.Request. Query params must be passed through template context in site.go.
Controllers
The website has 14 controllers:
| Controller | Purpose |
|---|---|
acl |
Access control list management |
api |
REST API endpoints |
audit |
Audit log viewing |
auth |
Authentication (login, signup, password reset) |
chat |
AI conversation interface |
collections |
Dynamic collection management |
dashboard |
Admin dashboard |
files |
File upload and management |
health |
Health check endpoint |
pages |
Page CRUD operations |
partials |
Reusable template partials |
setup |
Initial setup wizard |
site |
Public site rendering |
workspace |
User workspace management |