5229ccdb0fd366f8cf93c919071ae2af6f39e362
Dynamic Sites
An LLM-powered website editing framework. Edit your site via SMS, a web API, or a visual editor — all driven by natural language.
Architecture
┌─────────────────────────────────────────────────┐
│ Channels │
│ SMS (Vonage) │ POST /api/edit │ /editor │
└───────┬─────────┴────────┬─────────┴─────┬──────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────┐
│ Single Container (entrypoint.sh) │
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ Orchestrator (Express, port 3001) │ │
│ │ │ │
│ │ Webhook ──► Idempotency ──► Rate Limit │ │
│ │ │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ In-Process FIFO Queue (concurrency 1) │ │
│ │ │ propose: route ► LLM ► proposal │ │ │
│ │ │ apply: validate ► writeContentFile│ │ │
│ │ └──────────────────────────────────────┘ │ │
│ │ │ │
│ │ SQLite: idempotency, proposals, rate │ │
│ │ limits, audit log │ │
│ └──────────────────┬─────────────────────────┘ │
│ │ writes canonical JSON │
│ ▼ │
│ ┌──────────────────┐ │
│ │ content/ (JSON) │ shared filesystem │
│ │ site-context.json│ │
│ └────────┬─────────┘ │
│ │ reads with TTL cache │
│ ▼ │
│ ┌────────────────────────────────────────────┐ │
│ │ Astro SSR (port 4321) │ │
│ │ Homepage + Editor │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
Quick Start
Prerequisites
- Node.js 22+
- npm
- A Vonage API account with a Messages API-enabled application
Local Development
# Clone and install
npm install
# Start the Astro dev server (port 4321)
npm run dev
# In another terminal, start the orchestrator (port 3001)
npm run dev:server
# Visit http://localhost:4321 — the demo site renders from fixtures
# Visit http://localhost:4321/editor — log in with API_EDIT_SECRET
Environment Variables
Copy .env.example to .env and set at minimum:
API_EDIT_SECRET— shared secret for API auth and editor loginOLLAMA_API_KEY— required for LLM-powered editsVONAGE_API_KEY— your Vonage API key (from Dashboard)VONAGE_API_SECRET— your Vonage API secret (from Dashboard)VONAGE_APPLICATION_ID— your Vonage application IDVONAGE_PRIVATE_KEY_PATH— path to theprivate.keyfile on disk, or setVONAGE_PRIVATE_KEYto the base64-encoded PEM contentVONAGE_API_SIGNATURE_SECRET— webhook signature secret (from Dashboard → API Settings)
See .env.example for all options.
Vonage Setup
- Create a Vonage application in the Dashboard with Messages capability enabled.
- Set the inbound message webhook URL to
https://dynamicsites.kadil.dev/webhooks/inbound(POST). - Set the status webhook URL to
https://dynamicsites.kadil.dev/webhooks/status(POST). - Under API Settings, ensure Messages API is set as the default for SMS.
- Copy the generated
private.keyto the project root (or base64-encode it forVONAGE_PRIVATE_KEY). - Note your signature secret from Dashboard → API Settings for webhook verification.
Docker
docker compose build
docker compose up -d
# Site: http://localhost:4321
# Orchestrator: http://localhost:3001/health
Both the Astro SSR site and the orchestrator run in a single container, sharing the same content/ directory. The entrypoint script starts both processes and seeds content from the image into the volume on first deploy.
Project Structure
├── content/ # Canonical JSON content (the "database")
│ ├── sections/ # One JSON file per site section
│ ├── events.json # Upcoming events
│ └── .backups/ # Pre-apply backups (auto-managed)
├── config/
│ └── sms-sites.json # SMS routing allowlist
├── site-context.json # Brand tone, style, LLM prompt context
├── shared/ # Zod schemas + canonical JSON (workspace pkg)
│ └── src/
│ ├── schemas/index.ts # All Zod schemas (the contract)
│ ├── canonical-json.ts # Sorted-key JSON serialization
│ └── repo-validation.ts# Path → schema mapping
├── server/ # Orchestrator (workspace pkg)
│ └── src/
│ ├── index.ts # Entrypoint + graceful shutdown
│ ├── app.ts # Express app factory
│ ├── db.ts # SQLite (idempotency, proposals, audit)
│ ├── logger.ts # Structured logging (pino)
│ ├── queue/ # FIFO queue + job processor
│ ├── routes/ # API edit, SMS webhook, health
│ ├── llm/ # Ollama client with retry/validation
│ ├── sms/ # Vonage parse, reply, templates
│ └── io/ # Filesystem writer (atomic, with backup)
├── src/ # Astro SSR site
│ ├── pages/
│ │ ├── index.astro # Homepage (renders from content/)
│ │ └── editor.astro # Editor (auth-gated React island)
│ ├── lib/
│ │ ├── site-bundle.ts # Content parser + validator
│ │ └── site-data.ts # Disk reader with TTL cache
│ ├── layouts/
│ │ └── BaseLayout.astro
│ └── components/
│ ├── sections/ # Astro section components
│ └── editor/ # React editor island
├── scripts/ # CLI tools
├── Dockerfile # Combined image (Astro SSR + orchestrator)
├── docker-compose.yml # Full stack (single service)
└── entrypoint.sh # Starts both processes + seeds content
Edit Flow
- User sends a natural language message (SMS, HTTP, or editor)
- Route: LLM determines which content file to edit
- Propose: LLM generates new JSON + plain-language summary
- Confirm: User replies YES/NO (SMS) or clicks confirm (editor/HTTP)
- Apply: Validated JSON is written to disk via atomic write
- Live: Astro SSR picks up the change on next request (TTL cache)
Key Design Decisions
- Single container: Both Astro SSR and the orchestrator run in one container, sharing the filesystem for content reads and writes
- No Redis, no BullMQ: Simple in-process FIFO queue with concurrency 1
- No git: Content persistence is filesystem-only
- SQLite for everything: Idempotency, proposals, rate limits, audit log
- Zod is the contract: Schemas drive validation at every boundary
- Atomic writes: temp file + rename prevents partial writes
- Pre-write backups: Last 20 versions per file under
content/.backups/
Description
Languages
TypeScript
57.5%
Astro
34.8%
JavaScript
2.8%
CSS
2.7%
Dockerfile
1.2%
Other
1%