- Add session_id, model, allowed_tools to Claude request handler - Update OpenAPI spec for Claude endpoint - Fix BuildExecutor constructor call sites - Rewrite landing-test.sh for agent-driven flow - Fix cookbook documentation for correct API format Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
600 lines
20 KiB
Bash
Executable File
600 lines
20 KiB
Bash
Executable File
#!/bin/bash
|
|
# Landing Page Cookbook Test Script
|
|
# Tests the full agent-driven landing page flow from cookbooks/landing-page.md
|
|
#
|
|
# Usage:
|
|
# ./cookbooks/scripts/landing-test.sh run [name] # Run the full flow
|
|
# ./cookbooks/scripts/landing-test.sh teardown [name] # Clean up test resources
|
|
# ./cookbooks/scripts/landing-test.sh status [name] # Check current status
|
|
|
|
set -euo pipefail
|
|
|
|
# Configuration
|
|
API_URL="${RDEV_API_URL:-https://rdev.masq-ops.orchard9.ai}"
|
|
API_KEY="${RDEV_API_KEY:?RDEV_API_KEY environment variable required}"
|
|
|
|
# Colors
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
BLUE='\033[0;34m'
|
|
NC='\033[0m' # No Color
|
|
|
|
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
|
log_success() { echo -e "${GREEN}[OK]${NC} $1"; }
|
|
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
|
|
|
# Timeouts
|
|
BUILD_TIMEOUT=180 # 3 minutes for Claude to build the site
|
|
BUILD_POLL_INTERVAL=5 # Check every 5 seconds
|
|
PIPELINE_TIMEOUT=300 # 5 minutes max wait for CI pipeline
|
|
PIPELINE_POLL_INTERVAL=10
|
|
SITE_TIMEOUT=60 # 1 minute max wait for site to be live
|
|
|
|
api_call() {
|
|
local method="$1"
|
|
local endpoint="$2"
|
|
local data="${3:-}"
|
|
|
|
if [[ -n "$data" ]]; then
|
|
curl -s -X "$method" "${API_URL}${endpoint}" \
|
|
-H "X-API-Key: ${API_KEY}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$data"
|
|
else
|
|
curl -s -X "$method" "${API_URL}${endpoint}" \
|
|
-H "X-API-Key: ${API_KEY}"
|
|
fi
|
|
}
|
|
|
|
check_health() {
|
|
log_info "Checking API health..."
|
|
local response
|
|
response=$(curl -s "${API_URL}/health")
|
|
if echo "$response" | jq -e '.data.status == "ok"' > /dev/null 2>&1; then
|
|
log_success "API is healthy"
|
|
return 0
|
|
else
|
|
log_error "API health check failed"
|
|
echo "$response" | jq .
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# Wait for build to complete (Claude building the site)
|
|
# Returns: 0 on success, 1 on failure/timeout
|
|
wait_for_build() {
|
|
local task_id="$1"
|
|
local start_time=$(date +%s)
|
|
|
|
log_info "Waiting for Claude to build the site (timeout: ${BUILD_TIMEOUT}s)..."
|
|
|
|
while true; do
|
|
local elapsed=$(($(date +%s) - start_time))
|
|
if [[ $elapsed -ge $BUILD_TIMEOUT ]]; then
|
|
log_error "Build timeout after ${BUILD_TIMEOUT}s"
|
|
return 1
|
|
fi
|
|
|
|
local response
|
|
response=$(api_call GET "/builds/$task_id" 2>/dev/null || echo "{}")
|
|
|
|
local status
|
|
status=$(echo "$response" | jq -r '.data.status // "unknown"' 2>/dev/null)
|
|
|
|
case "$status" in
|
|
completed)
|
|
local success
|
|
success=$(echo "$response" | jq -r '.data.result.success // false')
|
|
if [[ "$success" == "true" ]]; then
|
|
log_success "Build completed successfully (${elapsed}s)"
|
|
echo "$response" | jq '.data.result | {success, commit_sha, files_changed, duration_ms}'
|
|
return 0
|
|
else
|
|
log_error "Build completed but failed"
|
|
echo "$response" | jq '.data.result'
|
|
return 1
|
|
fi
|
|
;;
|
|
failed)
|
|
log_error "Build failed"
|
|
echo "$response" | jq '.data.result // .data'
|
|
return 1
|
|
;;
|
|
running)
|
|
echo -ne "\r${BLUE}[INFO]${NC} Build status: running (${elapsed}s)... "
|
|
;;
|
|
pending)
|
|
echo -ne "\r${BLUE}[INFO]${NC} Build status: pending (${elapsed}s)... "
|
|
;;
|
|
*)
|
|
echo -ne "\r${BLUE}[INFO]${NC} Build status: $status (${elapsed}s)... "
|
|
;;
|
|
esac
|
|
|
|
sleep $BUILD_POLL_INTERVAL
|
|
done
|
|
}
|
|
|
|
# Wait for pipeline to appear and complete
|
|
# Returns: 0 on success, 1 on failure/timeout
|
|
wait_for_pipeline() {
|
|
local project_name="$1"
|
|
local start_time=$(date +%s)
|
|
local pipeline_found=false
|
|
local pipeline_number=""
|
|
local pipeline_status=""
|
|
|
|
log_info "Waiting for CI pipeline to start (timeout: ${PIPELINE_TIMEOUT}s)..."
|
|
|
|
while true; do
|
|
local elapsed=$(($(date +%s) - start_time))
|
|
if [[ $elapsed -ge $PIPELINE_TIMEOUT ]]; then
|
|
log_error "Pipeline timeout after ${PIPELINE_TIMEOUT}s"
|
|
return 1
|
|
fi
|
|
|
|
local response
|
|
response=$(api_call GET "/projects/$project_name/pipelines" 2>/dev/null || echo "{}")
|
|
|
|
# Check if we have pipelines (API returns array at .data)
|
|
local pipeline_count
|
|
pipeline_count=$(echo "$response" | jq -r '.data | length' 2>/dev/null || echo "0")
|
|
|
|
if [[ "$pipeline_count" -gt 0 ]]; then
|
|
if [[ "$pipeline_found" == "false" ]]; then
|
|
pipeline_found=true
|
|
pipeline_number=$(echo "$response" | jq -r '.data[0].number')
|
|
log_success "Pipeline #$pipeline_number started"
|
|
fi
|
|
|
|
# Get latest pipeline status
|
|
pipeline_status=$(echo "$response" | jq -r '.data[0].status')
|
|
|
|
case "$pipeline_status" in
|
|
success)
|
|
log_success "Pipeline #$pipeline_number completed successfully (${elapsed}s)"
|
|
return 0
|
|
;;
|
|
failure|error|killed|declined)
|
|
log_error "Pipeline #$pipeline_number failed with status: $pipeline_status"
|
|
echo "$response" | jq '.data[0]'
|
|
return 1
|
|
;;
|
|
running|pending)
|
|
echo -ne "\r${BLUE}[INFO]${NC} Pipeline #$pipeline_number status: $pipeline_status (${elapsed}s)... "
|
|
;;
|
|
*)
|
|
echo -ne "\r${BLUE}[INFO]${NC} Pipeline #$pipeline_number status: $pipeline_status (${elapsed}s)... "
|
|
;;
|
|
esac
|
|
else
|
|
echo -ne "\r${BLUE}[INFO]${NC} Waiting for pipeline to start (${elapsed}s)... "
|
|
fi
|
|
|
|
sleep $PIPELINE_POLL_INTERVAL
|
|
done
|
|
}
|
|
|
|
# Wait for site to be accessible
|
|
# Returns: 0 on success, 1 on failure/timeout
|
|
wait_for_site() {
|
|
local domain="$1"
|
|
local start_time=$(date +%s)
|
|
|
|
log_info "Waiting for site to be live: https://$domain (timeout: ${SITE_TIMEOUT}s)..."
|
|
|
|
while true; do
|
|
local elapsed=$(($(date +%s) - start_time))
|
|
if [[ $elapsed -ge $SITE_TIMEOUT ]]; then
|
|
log_warn "Site timeout after ${SITE_TIMEOUT}s - may still be deploying"
|
|
return 1
|
|
fi
|
|
|
|
local http_code
|
|
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "https://$domain" 2>/dev/null || echo "000")
|
|
|
|
if [[ "$http_code" == "200" ]]; then
|
|
echo ""
|
|
log_success "Site is live! HTTP $http_code (${elapsed}s)"
|
|
return 0
|
|
elif [[ "$http_code" == "000" ]]; then
|
|
echo -ne "\r${BLUE}[INFO]${NC} Waiting for site... (${elapsed}s, connection failed) "
|
|
else
|
|
echo -ne "\r${BLUE}[INFO]${NC} Waiting for site... (${elapsed}s, HTTP $http_code) "
|
|
fi
|
|
|
|
sleep 3
|
|
done
|
|
}
|
|
|
|
# Test adding a DNS alias
|
|
test_dns_alias() {
|
|
local project_name="$1"
|
|
local alias_domain="$2"
|
|
|
|
log_info "Testing DNS alias: $alias_domain"
|
|
|
|
local response
|
|
response=$(api_call POST "/projects/$project_name/domains" "{\"domain\": \"$alias_domain\"}")
|
|
|
|
if echo "$response" | jq -e '.error' > /dev/null 2>&1; then
|
|
local error_code
|
|
error_code=$(echo "$response" | jq -r '.error.code // "UNKNOWN"')
|
|
if [[ "$error_code" == "DOMAIN_EXISTS" ]]; then
|
|
log_warn "Domain alias already exists: $alias_domain"
|
|
return 0
|
|
fi
|
|
log_error "Failed to add DNS alias"
|
|
echo "$response" | jq .
|
|
return 1
|
|
fi
|
|
|
|
log_success "DNS alias added: $alias_domain"
|
|
echo "$response" | jq '.data | {domain, type, record_type}'
|
|
return 0
|
|
}
|
|
|
|
# Remove a DNS alias
|
|
remove_dns_alias() {
|
|
local project_name="$1"
|
|
local alias_domain="$2"
|
|
|
|
log_info "Removing DNS alias: $alias_domain"
|
|
|
|
local response
|
|
response=$(api_call DELETE "/projects/$project_name/domains/$alias_domain")
|
|
|
|
if echo "$response" | jq -e '.error' > /dev/null 2>&1; then
|
|
local error_code
|
|
error_code=$(echo "$response" | jq -r '.error.code // "UNKNOWN"')
|
|
if [[ "$error_code" == "NOT_FOUND" ]]; then
|
|
log_warn "Domain alias not found (already deleted?): $alias_domain"
|
|
return 0
|
|
fi
|
|
log_warn "Failed to remove DNS alias: $alias_domain"
|
|
return 1
|
|
fi
|
|
|
|
log_success "DNS alias removed: $alias_domain"
|
|
return 0
|
|
}
|
|
|
|
run_flow() {
|
|
local project_name="${1:-landing-test}"
|
|
|
|
# Default prompt for building a landing page
|
|
local build_prompt="Build a modern landing page with: dark gradient background (#1a1a2e to #16213e), centered hero section with company name 'Acme Corp' and tagline 'Building the future', email signup form with a submit button, responsive design for mobile. Use vanilla HTML/CSS/JS. Create index.html, styles.css, and a Dockerfile that serves with nginx on port 80."
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " Landing Page Cookbook Test"
|
|
echo " Project: $project_name"
|
|
echo " Flow: Agent-driven (create-and-build)"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
# Step 0: Health check
|
|
check_health || exit 1
|
|
echo ""
|
|
|
|
# Step 1: Create project AND enqueue build in one call
|
|
log_info "Step 1: Creating project and enqueuing build task..."
|
|
log_info "Prompt: ${build_prompt:0:80}..."
|
|
|
|
local create_payload
|
|
create_payload=$(jq -n \
|
|
--arg name "$project_name" \
|
|
--arg desc "Cookbook test: agent-driven landing page" \
|
|
--arg prompt "$build_prompt" \
|
|
'{
|
|
name: $name,
|
|
description: $desc,
|
|
prompt: $prompt,
|
|
auto_commit: true,
|
|
auto_push: true
|
|
}')
|
|
|
|
local create_response
|
|
create_response=$(api_call POST "/project/create-and-build" "$create_payload")
|
|
|
|
if echo "$create_response" | jq -e '.error' > /dev/null 2>&1; then
|
|
log_error "Failed to create project"
|
|
echo "$create_response" | jq .
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Project created and build enqueued"
|
|
echo "$create_response" | jq '.data | {
|
|
project_id,
|
|
domain,
|
|
url,
|
|
git: .git.html_url,
|
|
task_id,
|
|
status,
|
|
status_url
|
|
}'
|
|
|
|
# Extract key info
|
|
local primary_domain
|
|
local task_id
|
|
primary_domain=$(echo "$create_response" | jq -r '.data.domain')
|
|
task_id=$(echo "$create_response" | jq -r '.data.task_id')
|
|
|
|
if [[ -z "$task_id" || "$task_id" == "null" ]]; then
|
|
log_error "No task_id returned - build was not enqueued"
|
|
exit 1
|
|
fi
|
|
|
|
log_success "Build task ID: $task_id"
|
|
echo ""
|
|
|
|
# Step 2: Monitor build progress (Claude building the site)
|
|
log_info "Step 2: Monitoring build progress..."
|
|
echo ""
|
|
local build_success=false
|
|
if wait_for_build "$task_id"; then
|
|
build_success=true
|
|
else
|
|
log_error "Build did not complete successfully"
|
|
log_info "Check build details: curl -s \"\$RDEV_API_URL/builds/$task_id\" -H \"X-API-Key: \$RDEV_API_KEY\" | jq ."
|
|
fi
|
|
echo ""
|
|
|
|
# Step 3: Monitor CI pipeline (only if build succeeded)
|
|
local pipeline_success=false
|
|
if [[ "$build_success" == "true" ]]; then
|
|
log_info "Step 3: Monitoring CI pipeline..."
|
|
if wait_for_pipeline "$project_name"; then
|
|
pipeline_success=true
|
|
else
|
|
log_warn "Pipeline did not complete successfully"
|
|
log_info "Check Woodpecker: https://ci.threesix.ai/threesix/$project_name"
|
|
fi
|
|
else
|
|
log_info "Step 3: Skipping pipeline monitoring (build failed)"
|
|
fi
|
|
echo ""
|
|
|
|
# Step 4: Verify site is live
|
|
local site_live=false
|
|
if [[ "$pipeline_success" == "true" ]]; then
|
|
log_info "Step 4: Verifying site is accessible..."
|
|
if wait_for_site "$primary_domain"; then
|
|
site_live=true
|
|
# Show a snippet of the response
|
|
log_info "Fetching site content preview..."
|
|
curl -s "https://$primary_domain" | head -20 | grep -E '<title>|<h1' || true
|
|
fi
|
|
else
|
|
log_info "Step 4: Skipping site verification (pipeline not successful)"
|
|
fi
|
|
echo ""
|
|
|
|
# Step 5: Test adding custom domains
|
|
local test_alias="${project_name}-alias.threesix.ai"
|
|
log_info "Step 5: Testing custom domain functionality..."
|
|
if test_dns_alias "$project_name" "$test_alias"; then
|
|
log_success "Domain alias test passed"
|
|
# Clean up test alias
|
|
sleep 2
|
|
remove_dns_alias "$project_name" "$test_alias"
|
|
else
|
|
log_warn "Domain alias test failed - check Cloudflare permissions"
|
|
fi
|
|
echo ""
|
|
|
|
# List all domains
|
|
log_info "Listing all project domains..."
|
|
local domains_response
|
|
domains_response=$(api_call GET "/projects/$project_name/domains")
|
|
if echo "$domains_response" | jq -e '.data.domains' > /dev/null 2>&1; then
|
|
echo "$domains_response" | jq '.data.domains[] | {domain, type, verified}'
|
|
fi
|
|
echo ""
|
|
|
|
# Summary
|
|
echo "=========================================="
|
|
echo " Test Results Summary"
|
|
echo "=========================================="
|
|
echo ""
|
|
echo " Project: $project_name"
|
|
echo " Task ID: $task_id"
|
|
echo " Git repo: $(echo "$create_response" | jq -r '.data.git.html_url // "N/A"')"
|
|
echo " Primary: https://$primary_domain"
|
|
echo ""
|
|
echo " Test Results:"
|
|
echo -e " Project created: ${GREEN}PASS${NC}"
|
|
if [[ "$build_success" == "true" ]]; then
|
|
echo -e " Agent build: ${GREEN}PASS${NC}"
|
|
else
|
|
echo -e " Agent build: ${RED}FAIL${NC}"
|
|
fi
|
|
if [[ "$pipeline_success" == "true" ]]; then
|
|
echo -e " CI Pipeline: ${GREEN}PASS${NC}"
|
|
elif [[ "$build_success" == "true" ]]; then
|
|
echo -e " CI Pipeline: ${RED}FAIL${NC}"
|
|
else
|
|
echo -e " CI Pipeline: ${YELLOW}SKIPPED${NC}"
|
|
fi
|
|
if [[ "$site_live" == "true" ]]; then
|
|
echo -e " Site accessible: ${GREEN}PASS${NC}"
|
|
elif [[ "$pipeline_success" == "true" ]]; then
|
|
echo -e " Site accessible: ${YELLOW}PENDING${NC}"
|
|
else
|
|
echo -e " Site accessible: ${YELLOW}SKIPPED${NC}"
|
|
fi
|
|
echo -e " Custom domains: ${GREEN}TESTED${NC}"
|
|
echo ""
|
|
echo " Useful commands:"
|
|
echo " Build status: curl -s \"\$RDEV_API_URL/builds/$task_id\" -H \"X-API-Key: \$RDEV_API_KEY\" | jq .data"
|
|
echo " Check status: ./cookbooks/scripts/landing-test.sh status $project_name"
|
|
echo " View logs: ./scripts/logs.sh -e"
|
|
echo " Woodpecker: https://ci.threesix.ai/threesix/$project_name"
|
|
echo " Teardown: ./cookbooks/scripts/landing-test.sh teardown $project_name"
|
|
echo ""
|
|
|
|
# Return appropriate exit code
|
|
if [[ "$build_success" == "true" && "$pipeline_success" == "true" && "$site_live" == "true" ]]; then
|
|
log_success "Full E2E test PASSED"
|
|
return 0
|
|
elif [[ "$build_success" == "true" && "$pipeline_success" == "true" ]]; then
|
|
log_warn "Partial success - build and pipeline passed but site not yet live"
|
|
return 0
|
|
elif [[ "$build_success" == "true" ]]; then
|
|
log_warn "Partial success - build passed but pipeline failed"
|
|
return 1
|
|
else
|
|
log_error "E2E test FAILED - build did not complete"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
teardown() {
|
|
local project_name="${1:-landing-test}"
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo " Teardown: $project_name"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
# First, list domains that will be deleted
|
|
log_info "Checking domains to be deleted..."
|
|
local domains_response
|
|
domains_response=$(api_call GET "/projects/$project_name/domains")
|
|
|
|
if echo "$domains_response" | jq -e '.data.total' > /dev/null 2>&1; then
|
|
local domain_count
|
|
domain_count=$(echo "$domains_response" | jq -r '.data.total')
|
|
log_info "Will delete $domain_count domain(s):"
|
|
echo "$domains_response" | jq -r '.data.domains[]? | " - \(.domain)"'
|
|
echo ""
|
|
fi
|
|
|
|
log_info "Deleting project $project_name..."
|
|
local response
|
|
response=$(api_call DELETE "/project/$project_name")
|
|
|
|
if echo "$response" | jq -e '.data.status == "deleted"' > /dev/null 2>&1; then
|
|
log_success "Project deleted"
|
|
echo "$response" | jq .data
|
|
elif echo "$response" | jq -e '.error.code == "NOT_FOUND"' > /dev/null 2>&1; then
|
|
log_warn "Project not found (already deleted?)"
|
|
else
|
|
log_error "Failed to delete project"
|
|
echo "$response" | jq .
|
|
exit 1
|
|
fi
|
|
echo ""
|
|
|
|
log_info "Note: Gitea repo is preserved for safety. Delete manually if needed:"
|
|
echo " https://git.threesix.ai/threesix/$project_name/settings"
|
|
echo ""
|
|
}
|
|
|
|
status() {
|
|
local project_name="${1:-landing-test}"
|
|
|
|
echo ""
|
|
log_info "Fetching status for: $project_name"
|
|
echo ""
|
|
|
|
# Get project info
|
|
local response
|
|
response=$(api_call GET "/project/$project_name")
|
|
|
|
if echo "$response" | jq -e '.error' > /dev/null 2>&1; then
|
|
log_error "Project not found or error"
|
|
echo "$response" | jq .
|
|
exit 1
|
|
fi
|
|
|
|
echo "$response" | jq '.data | {
|
|
name,
|
|
description,
|
|
domain,
|
|
url,
|
|
git: .git.html_url,
|
|
deployment
|
|
}'
|
|
|
|
echo ""
|
|
log_info "Listing all domains..."
|
|
local domains_response
|
|
domains_response=$(api_call GET "/projects/$project_name/domains")
|
|
|
|
if echo "$domains_response" | jq -e '.data.domains' > /dev/null 2>&1; then
|
|
echo "$domains_response" | jq '.data.domains'
|
|
fi
|
|
|
|
echo ""
|
|
log_info "Checking recent builds..."
|
|
local builds_response
|
|
builds_response=$(api_call GET "/projects/$project_name/builds?limit=3")
|
|
|
|
if echo "$builds_response" | jq -e '.data.builds' > /dev/null 2>&1; then
|
|
echo "$builds_response" | jq '.data.builds[] | {task_id, status, started_at, result: .result.success}'
|
|
else
|
|
log_info "No builds found"
|
|
fi
|
|
|
|
echo ""
|
|
log_info "Checking recent pipelines..."
|
|
local pipelines_response
|
|
pipelines_response=$(api_call GET "/projects/$project_name/pipelines")
|
|
|
|
if echo "$pipelines_response" | jq -e '.data | length > 0' > /dev/null 2>&1; then
|
|
echo "$pipelines_response" | jq '.data[0:3][] | {number, status, branch, commit: .commit[0:8]}'
|
|
else
|
|
log_info "No pipelines found"
|
|
fi
|
|
}
|
|
|
|
# Main
|
|
case "${1:-}" in
|
|
run)
|
|
shift
|
|
run_flow "${1:-landing-test}"
|
|
;;
|
|
teardown)
|
|
shift
|
|
teardown "${1:-landing-test}"
|
|
;;
|
|
status)
|
|
shift
|
|
status "${1:-landing-test}"
|
|
;;
|
|
*)
|
|
echo "Usage: $0 {run|teardown|status} [project-name]"
|
|
echo ""
|
|
echo "Commands:"
|
|
echo " run [name] Create project with agent-driven build and run full E2E flow"
|
|
echo " teardown [name] Delete project and clean up"
|
|
echo " status [name] Check current project status, builds, and pipelines"
|
|
echo ""
|
|
echo "E2E Flow (matches cookbooks/landing-page.md):"
|
|
echo " 1. POST /project/create-and-build - Create project + enqueue agent build"
|
|
echo " 2. GET /builds/{task_id} - Monitor Claude building the site"
|
|
echo " 3. GET /projects/{id}/pipelines - Monitor CI pipeline"
|
|
echo " 4. Verify site is live (HTTP 200)"
|
|
echo " 5. Test custom domains (POST/DELETE /projects/{id}/domains)"
|
|
echo " 6. DELETE /project/{name} - Teardown"
|
|
echo ""
|
|
echo "Timeouts:"
|
|
echo " Build: ${BUILD_TIMEOUT}s, Pipeline: ${PIPELINE_TIMEOUT}s, Site: ${SITE_TIMEOUT}s"
|
|
echo ""
|
|
echo "Environment:"
|
|
echo " RDEV_API_URL API endpoint (default: https://rdev.masq-ops.orchard9.ai)"
|
|
echo " RDEV_API_KEY API key (required)"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo " $0 run # Run with default project name 'landing-test'"
|
|
echo " $0 run my-landing # Run with custom project name"
|
|
echo " $0 status my-landing # Check status, builds, and pipelines"
|
|
echo " $0 teardown my-landing # Clean up project"
|
|
exit 1
|
|
;;
|
|
esac
|