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>
11 KiB
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
- rdev-api running with infrastructure handlers (v0.7.1+)
- Gitea at https://git.threesix.ai
- Woodpecker CI at https://ci.threesix.ai
- Zot registry at zot.threesix.svc.cluster.local:5000
projectsnamespace in K8s with RBAC- 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
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:
{
"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
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:
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:
- Claude receives the prompt in the claudebox
- Claude clones the repo to
/tmp/landing - Claude generates the Astro project files
- Claude commits and pushes to Gitea
Step 4: Monitor Build
Watch Woodpecker for the build:
Or via API:
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
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:
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
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)
# 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
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)
-
Add Woodpecker activation to rdev-api
- Store WOODPECKER_API_TOKEN in secrets
- Call Woodpecker API after creating Gitea repo
- Create webhook automatically
-
Claudebox code generation
- Spawn claudebox with project context
- Claudebox has Gitea SSH key
- Claude Code generates code based on prompt
- Auto-push to Gitea
-
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:
# 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:
# 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 - Infrastructure plan
- woodpecker-pipeline-template.yaml - CI template