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

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
  • projects namespace 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:

  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:

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)

  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:

# 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"