rdev/cookbooks/landing-page.md
jordan 39df51defd feat: Add multi-provider code agent interface with Claude Code and OpenCode adapters
Implements weeks 1-4 of the multi-provider architecture:

Week 1 - Foundation:
- Add domain models (AgentProvider, AgentRequest, AgentEvent, AgentResult)
- Define CodeAgent port interface with Execute, Cancel, Capabilities
- Create thread-safe provider registry with first-registered default

Week 2 - Claude Code Adapter:
- Extract kubectl exec logic into CodeAgent implementation
- Parse stream-json output format (init, message, tool_use, result)
- Support session continuation via --resume flag

Week 3 - OpenCode Adapter:
- HTTP/SSE client for opencode serve API
- Session management (create, send message, abort)
- Event streaming with documented buffer rationale

Week 4 - Quality & Polish:
- Fix race condition in OpenCode Cancel method
- Add AgentRequest.Validate() with ErrPromptRequired, ErrInvalidTimeout
- Document DefaultAvailabilityTimeout constants
- Add HTTP error context for debugging

Also includes:
- Work queue system with PostgreSQL adapter
- Credential store for infrastructure secrets
- Project templates with Woodpecker CI integration
- Comprehensive test coverage

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-27 09:25:51 -07:00

343 lines
11 KiB
Markdown

# Landing Page Cookbook
> Deploy a static landing page through the threesix.ai infrastructure with agent-driven development.
## Overview
This cookbook creates and deploys a simple landing page using the full threesix.ai autonomous infrastructure:
```
rdev-api → Gitea repo → Claude agent → push → Woodpecker CI → K8s deployment
```
**Target:** `landing.threesix.ai` (with future DNS aliases for www/root)
**Stack:** Astro (static site generator)
**Status:** Coming Soon page
---
## Current Architecture Gap
**Two separate systems that need bridging:**
| System | Endpoint | What it manages |
|--------|----------|-----------------|
| Project Management | `POST /project` | Gitea repos, DNS records, K8s deployments |
| Claudebox Execution | `POST /projects/{id}/claude` | Code generation in existing claudebox pods |
**The problem:** Creating a project via `POST /project` creates a Gitea repo, but there's no claudebox to generate code for it. The claudebox system only knows about pre-existing pods (pantheon, aeries).
**The solution:** Use an existing claudebox as a "worker" to clone, build, and push to any project repo.
---
## Prerequisites
### Credentials Required
| Secret | Location | Purpose |
|--------|----------|---------|
| RDEV_ADMIN_KEY | `rdev-credentials` secret | rdev-api authentication |
| GITEA_TOKEN | `rdev-credentials` secret | Gitea API access |
| WOODPECKER_API_TOKEN | `.secrets` file | Woodpecker repo activation |
| CLOUDFLARE_API_TOKEN | `rdev-credentials` secret | DNS management |
### Infrastructure Required
- [x] rdev-api running with infrastructure handlers (v0.7.1+)
- [x] Gitea at https://git.threesix.ai
- [x] Woodpecker CI at https://ci.threesix.ai
- [x] Zot registry at zot.threesix.svc.cluster.local:5000
- [x] `projects` namespace in K8s with RBAC
- [x] Wildcard TLS cert for *.threesix.ai
---
## Architecture
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Landing Page Flow │
│ │
│ 1. Create Project │
│ POST /project {"name": "www"} │
│ │ │
│ ├──▶ Creates Gitea repo: jordan/www │
│ └──▶ Creates DNS: www.threesix.ai → 208.122.204.172 │
│ │
│ 2. Activate Woodpecker │
│ POST /api/repos?forge_remote_id={id} │
│ │ │
│ └──▶ Creates webhook in Gitea │
│ │
│ 3. Generate Code (Claude Agent) │
│ claudebox or local Claude Code │
│ │ │
│ ├──▶ Creates Astro project │
│ ├──▶ Creates Dockerfile │
│ ├──▶ Creates .woodpecker.yml │
│ └──▶ Pushes to Gitea │
│ │
│ 4. CI/CD Pipeline (automatic) │
│ Woodpecker triggered by push │
│ │ │
│ ├──▶ Kaniko builds Docker image │
│ ├──▶ Pushes to Zot registry │
│ └──▶ Webhook triggers rdev-api deploy │
│ │
│ 5. Live at https://www.threesix.ai │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
---
## Step-by-Step Implementation
### Step 1: Create Project via rdev-api
```bash
RDEV_KEY="rdev_sk_prod_7f3a9c2e1d8b4a6f0e5c9d2b7a1f8e4c"
curl -X POST https://rdev.masq-ops.orchard9.ai/project \
-H "Authorization: Bearer $RDEV_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "landing", "description": "threesix.ai landing page"}'
```
**Response:**
```json
{
"data": {
"name": "landing",
"domain": "landing.threesix.ai",
"git": {
"clone_ssh": "git@git.threesix.ai:jordan/landing.git",
"clone_http": "https://git.threesix.ai/jordan/landing.git"
}
}
}
```
### Step 2: Activate Woodpecker CI
```bash
GITEA_TOKEN="5508ff241943e84aad0ced3559f5fbd311a2fb81"
WOODPECKER_TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0eXBlIjoidXNlciIsInVzZXItaWQiOiIxIn0.LcyVHcZ_gSvVH1w3y6TUCp_Jg9ubfsebOAVo-MtiNP8"
# Get Gitea repo ID
REPO_ID=$(curl -s https://git.threesix.ai/api/v1/repos/jordan/landing \
-H "Authorization: token $GITEA_TOKEN" | jq '.id')
# Activate in Woodpecker (creates webhook automatically)
curl -X POST "https://ci.threesix.ai/api/repos?forge_remote_id=$REPO_ID" \
-H "Authorization: Bearer $WOODPECKER_TOKEN"
```
### Step 3: Generate Code via Claudebox
Use the `pantheon` claudebox as a worker to generate code for the landing project:
```bash
RDEV_KEY="rdev_sk_prod_7f3a9c2e1d8b4a6f0e5c9d2b7a1f8e4c"
# Tell Claude to build the landing page in /tmp/landing
curl -X POST "https://rdev.masq-ops.orchard9.ai/projects/pantheon/claude" \
-H "Authorization: Bearer $RDEV_KEY" \
-H "Content-Type: application/json" \
-d '{
"prompt": "Clone https://git.threesix.ai/jordan/landing.git to /tmp/landing, then create a simple Astro landing page with: Coming Soon message, threesix.ai branding (dark theme), responsive layout, Dockerfile (nginx), and .woodpecker.yml for CI/CD. Commit and push when done."
}'
```
**What happens:**
1. Claude receives the prompt in the claudebox
2. Claude clones the repo to `/tmp/landing`
3. Claude generates the Astro project files
4. Claude commits and pushes to Gitea
### Step 4: Monitor Build
Watch Woodpecker for the build:
- https://ci.threesix.ai/jordan/landing
Or via API:
```bash
curl -s "https://ci.threesix.ai/api/repos/jordan/landing/pipelines" \
-H "Authorization: Bearer $WOODPECKER_TOKEN" | jq '.[0] | {number, status, started}'
```
### Step 5: Verify Deployment
```bash
curl -s "https://rdev.masq-ops.orchard9.ai/project/landing" \
-H "Authorization: Bearer $RDEV_KEY" | jq '.data.deployment'
```
Site live at: https://landing.threesix.ai
### Step 6: Configure DNS Aliases (Optional)
Point `www.threesix.ai` and `threesix.ai` to the landing page:
```bash
CF_TOKEN="nGoDhG6Za66XsKMl6W7LNXuowc5EM00glHxkq1KK"
CF_ZONE="e0bc8d510f62807b360db0c5994964c5"
# Update root A record to point to k3s cluster
curl -X PATCH "https://api.cloudflare.com/client/v4/zones/$CF_ZONE/dns_records/{record_id}" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "A",
"name": "threesix.ai",
"content": "208.122.204.172",
"proxied": false
}'
```
---
## File Templates
### .woodpecker.yml
```yaml
steps:
build:
image: gcr.io/kaniko-project/executor:latest
settings:
registry: zot.threesix.svc.cluster.local:5000
tags:
- ${CI_COMMIT_SHA:0:8}
- latest
repo: zot.threesix.svc.cluster.local:5000/${CI_REPO_NAME}
context: .
dockerfile: Dockerfile
insecure: true
when:
branch: main
notify:
image: alpine/curl:latest
commands:
- echo "Build complete, webhook will trigger deployment"
when:
branch: main
status: success
```
### Dockerfile (Astro + Nginx)
```dockerfile
# Build stage
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
```
### nginx.conf
```nginx
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
```
---
## Current Gaps & Future Automation
### What's Manual Today
| Step | Status | Automation Path |
|------|--------|-----------------|
| Create project | ✅ API | Already automated |
| Activate Woodpecker | 🔧 API call needed | Add to rdev-api |
| Generate code | ❌ Manual Claude | Claudebox integration |
| Push to Gitea | ❌ Manual git | Claudebox with SSH key |
| Deploy | ✅ Webhook | Already automated |
### To Fully Automate (Future Work)
1. **Add Woodpecker activation to rdev-api**
- Store WOODPECKER_API_TOKEN in secrets
- Call Woodpecker API after creating Gitea repo
- Create webhook automatically
2. **Claudebox code generation**
- Spawn claudebox with project context
- Claudebox has Gitea SSH key
- Claude Code generates code based on prompt
- Auto-push to Gitea
3. **Single API call**
```
POST /project/create-and-build
{
"name": "www",
"prompt": "Create an Astro landing page with coming soon message",
"stack": "astro"
}
```
---
## Verification
After deployment, verify:
```bash
# Check DNS
dig www.threesix.ai
# Check site
curl -I https://www.threesix.ai
# Check deployment status
curl https://rdev.masq-ops.orchard9.ai/project/www \
-H "Authorization: Bearer $RDEV_KEY"
```
---
## Rollback
To remove the landing page:
```bash
# Delete via rdev-api (removes Gitea repo, DNS, K8s deployment)
curl -X DELETE https://rdev.masq-ops.orchard9.ai/project/www \
-H "Authorization: Bearer $RDEV_KEY"
```
---
## Related
- [THREESIX_INFRASTRUCTURE.md](/Users/jordanwashburn/Workspace/orchard9/rdev/docs/plans/THREESIX_INFRASTRUCTURE.md) - Infrastructure plan
- [woodpecker-pipeline-template.yaml](../deployments/k8s/base/threesix/woodpecker-pipeline-template.yaml) - CI template