#!/usr/bin/env python3 """Code project harness for small business-owner app builds. This harness creates a real multi-file project instead of a one-file demo. It keeps the model out of fragile boilerplate and makes the product path return a project with files, tests, a change summary, and deterministic validation. """ from __future__ import annotations import difflib import json import re from dataclasses import dataclass, field from pathlib import Path from typing import Any @dataclass class CodeProjectSpec: project_name: str project_type: str description: str features: list[str] = field(default_factory=list) entities: list[str] = field(default_factory=list) fields: list[str] = field(default_factory=list) commands: list[str] = field(default_factory=list) PROJECT_DEFAULTS: dict[str, dict[str, list[str] | str]] = { "stripe_checkout": { "description": "Stripe-ready checkout starter with server-side session creation and webhook notes.", "features": ["Pricing cards", "Checkout session route", "Webhook verification notes", "Success and cancel states"], "entities": ["product", "checkout_session", "customer"], "fields": ["Product", "Price", "Billing email", "Status"], "commands": ["npm install", "npm run lint", "npm run test"], }, "booking_app": { "description": "Appointment booking starter with customer/service/date capture and local persistence.", "features": ["Booking form", "Appointment list", "Status updates", "CSV export"], "entities": ["appointment", "customer", "service"], "fields": ["Customer", "Service", "Date", "Time", "Status"], "commands": ["npm install", "npm run lint", "npm run test"], }, "crm_app": { "description": "Lead tracker starter for small businesses that need follow-up discipline.", "features": ["Lead form", "Pipeline status", "Follow-up date", "CSV export"], "entities": ["lead", "company", "follow_up"], "fields": ["Name", "Company", "Need", "Status", "Next Follow-up"], "commands": ["npm install", "npm run lint", "npm run test"], }, "dashboard": { "description": "Small business dashboard starter with metrics, tasks, and next actions.", "features": ["KPI cards", "Task list", "Recent activity", "Next action panel"], "entities": ["metric", "task", "activity"], "fields": ["Metric", "Value", "Owner", "Status"], "commands": ["npm install", "npm run lint", "npm run test"], }, "invoice_app": { "description": "Invoice builder starter for small businesses that need fast billing and payment tracking.", "features": ["Invoice form", "Client and service tracking", "Payment status", "CSV export"], "entities": ["invoice", "client", "service"], "fields": ["Client", "Service", "Amount", "Due Date", "Status"], "commands": ["npm install", "npm run lint", "npm run test"], }, "estimate_app": { "description": "Estimate builder starter for service businesses that need clear quotes and follow-up tracking.", "features": ["Estimate form", "Scope and price tracking", "Follow-up date", "CSV export"], "entities": ["estimate", "client", "job"], "fields": ["Client", "Job", "Scope", "Price", "Follow-up Date", "Status"], "commands": ["npm install", "npm run lint", "npm run test"], }, "content_calendar": { "description": "Content calendar starter for creators and solo founders planning posts, hooks, and publishing dates.", "features": ["Content idea capture", "Channel planning", "Publish date tracking", "CSV export"], "entities": ["content_item", "channel", "campaign"], "fields": ["Idea", "Channel", "Hook", "Publish Date", "Status"], "commands": ["npm install", "npm run lint", "npm run test"], }, "expense_tracker": { "description": "Expense tracker starter for owners who need fast cash and vendor visibility.", "features": ["Expense form", "Vendor and category tracking", "Payment method tracking", "CSV export"], "entities": ["expense", "vendor", "category"], "fields": ["Vendor", "Category", "Amount", "Payment Method", "Date", "Status"], "commands": ["npm install", "npm run lint", "npm run test"], }, "cloudflare_worker": { "description": "Cloudflare Worker API starter with validation, CORS, health check, tests, and safe environment handling.", "features": ["Health check", "Validated JSON endpoint", "CORS handling", "Worker tests", "Wrangler deployment notes"], "entities": ["request", "lead", "response"], "fields": ["Name", "Email", "Need", "Source"], "commands": ["npm install", "npm run dev", "npm run build", "npm run test", "npm run deploy"], }, } def clean_text(value: Any, fallback: str) -> str: if not isinstance(value, str): return fallback value = re.sub(r"\s+", " ", value).strip() return value or fallback def slugify(value: str) -> str: return re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-") or "kaiju-project" def pascal_case(value: str) -> str: words = re.findall(r"[A-Za-z0-9]+", value) return "".join(word[:1].upper() + word[1:] for word in words) or "KaijuProject" def infer_project_type(prompt: str) -> str: lower = prompt.lower() if "content calendar" in lower or "content planner" in lower or "posting calendar" in lower: return "content_calendar" if "expense tracker" in lower or "budget tracker" in lower or "cash tracker" in lower: return "expense_tracker" if "estimate app" in lower or "estimate builder" in lower or "quote app" in lower or "quote builder" in lower: return "estimate_app" if "invoice app" in lower or "invoice builder" in lower or "invoice tracker" in lower or "invoice generator" in lower: return "invoice_app" if "stripe" in lower or "checkout" in lower or "payment" in lower: return "stripe_checkout" if any(term in lower for term in ["cloudflare worker", "worker api", "wrangler", "d1", "r2", "durable object", "api worker", "telegram", "webhook", "file upload", "search proxy", "perplexity", "rate limit", "license check", "api key"]): return "cloudflare_worker" if "booking" in lower or "appointment" in lower or "schedule" in lower: return "booking_app" if "crm" in lower or "lead" in lower or "pipeline" in lower or "prospect" in lower: return "crm_app" return "dashboard" def infer_project_name(prompt: str, project_type: str) -> str: stop_words = r"\s+(?:for|with|to|that|using|include|including|built|as)\b" for marker in ["named", "called"]: match = re.search(rf"{marker}\s+([A-Z][A-Za-z0-9 &'&-]{{2,70}}?)(?:\.|,|{stop_words}|$)", prompt, flags=re.IGNORECASE) if match: return clean_text(match.group(1), "Kaiju Project").rstrip(".") names = { "stripe_checkout": "Checkout Starter", "booking_app": "Booking Desk", "crm_app": "Pipeline Desk", "dashboard": "Operator Dashboard", "invoice_app": "Invoice Desk", "estimate_app": "Estimate Desk", "content_calendar": "Content Desk", "expense_tracker": "Expense Desk", "cloudflare_worker": "Worker API", } return names[project_type] def spec_from_prompt(prompt: str) -> CodeProjectSpec: project_type = infer_project_type(prompt) defaults = PROJECT_DEFAULTS[project_type] features = list(defaults["features"]) entities = list(defaults["entities"]) fields = list(defaults["fields"]) lower = prompt.lower() if project_type == "cloudflare_worker": if "telegram" in lower: features.append("Telegram webhook route") entities.append("telegram_update") if "r2" in lower or "upload" in lower or "file" in lower: features.append("R2 file upload route") entities.append("uploaded_file") if "d1" in lower or "database" in lower or "persist" in lower or "store" in lower: features.append("D1 persistence notes") entities.append("database_record") if "search" in lower or "perplexity" in lower: features.append("Server-side search proxy") entities.append("search_query") if "rate limit" in lower or "license" in lower or "api key" in lower or "auth" in lower: features.append("Bearer auth and rate limit guard") entities.append("api_client") return CodeProjectSpec( project_name=infer_project_name(prompt, project_type), project_type=project_type, description=str(defaults["description"]), features=list(dict.fromkeys(features)), entities=list(dict.fromkeys(entities)), fields=fields, commands=list(defaults["commands"]), ) def normalize_spec(raw: dict[str, Any] | CodeProjectSpec, prompt: str = "") -> CodeProjectSpec: if isinstance(raw, CodeProjectSpec): spec = raw else: fallback = spec_from_prompt(prompt) features = raw.get("features") if isinstance(raw.get("features"), list) else fallback.features entities = raw.get("entities") if isinstance(raw.get("entities"), list) else fallback.entities fields = raw.get("fields") if isinstance(raw.get("fields"), list) else fallback.fields commands = raw.get("commands") if isinstance(raw.get("commands"), list) else fallback.commands spec = CodeProjectSpec( project_name=clean_text(raw.get("project_name"), fallback.project_name), project_type=clean_text(raw.get("project_type"), fallback.project_type).lower(), description=clean_text(raw.get("description"), fallback.description), features=[clean_text(item, "") for item in features if isinstance(item, str)][:8] or fallback.features, entities=[clean_text(item, "") for item in entities if isinstance(item, str)][:6] or fallback.entities, fields=[clean_text(item, "") for item in fields if isinstance(item, str)][:8] or fallback.fields, commands=[clean_text(item, "") for item in commands if isinstance(item, str)][:5] or fallback.commands, ) if spec.project_type not in PROJECT_DEFAULTS: spec.project_type = infer_project_type(prompt) if len(spec.features) < 3: spec.features = list(PROJECT_DEFAULTS[spec.project_type]["features"]) if len(spec.fields) < 3: spec.fields = list(PROJECT_DEFAULTS[spec.project_type]["fields"]) return spec def render_package_json(spec: CodeProjectSpec) -> str: if spec.project_type == "cloudflare_worker": package = { "name": slugify(spec.project_name), "version": "0.1.0", "private": True, "type": "module", "scripts": { "dev": "wrangler dev", "build": "wrangler deploy --dry-run", "deploy": "wrangler deploy", "test": "vitest run", "lint": "tsc --noEmit", }, "dependencies": { "zod": "^3.23.8", }, "devDependencies": { "@cloudflare/workers-types": "^4.20250501.0", "typescript": "^5.6.0", "vitest": "^2.1.0", "wrangler": "^4.0.0", }, } return json.dumps(package, indent=2) + "\n" package = { "name": slugify(spec.project_name), "version": "0.1.0", "private": True, "scripts": { "dev": "next dev", "build": "next build", "lint": "tsc --noEmit", "test": "vitest run", }, "dependencies": { "next": "^15.0.0", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^3.23.8", }, "devDependencies": { "@types/node": "^22.0.0", "@types/react": "^19.0.0", "@types/react-dom": "^19.0.0", "typescript": "^5.6.0", "vitest": "^2.1.0", }, } if spec.project_type == "stripe_checkout": package["dependencies"]["stripe"] = "^17.0.0" return json.dumps(package, indent=2) + "\n" def render_tsconfig() -> str: return """{ "compilerOptions": { "target": "ES2022", "lib": ["dom", "dom.iterable", "es2022"], "allowJs": false, "skipLibCheck": true, "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", "incremental": true, "plugins": [{ "name": "next" }] }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] } """ def render_next_config() -> str: return """/** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true }; module.exports = nextConfig; """ def render_worker_tsconfig() -> str: return """{ "compilerOptions": { "target": "ES2022", "module": "ESNext", "moduleResolution": "Bundler", "lib": ["ES2022"], "types": ["@cloudflare/workers-types"], "strict": true, "skipLibCheck": true, "noEmit": true }, "include": ["src/**/*.ts", "tests/**/*.ts"] } """ def worker_has(spec: CodeProjectSpec, *terms: str) -> bool: text = " ".join([spec.project_name, spec.description, *spec.features, *spec.entities, *spec.fields]).lower() return any(term.lower() in text for term in terms) def render_wrangler_toml(spec: CodeProjectSpec) -> str: bindings: list[str] = [] if worker_has(spec, "d1", "database", "persist"): bindings.append(""" [[d1_databases]] binding = "DB" database_name = "kaiju_dev" database_id = "replace-with-d1-database-id" """) if worker_has(spec, "r2", "upload", "file"): bindings.append(""" [[r2_buckets]] binding = "FILES_BUCKET" bucket_name = "kaiju-dev-files" """) return f"""name = "{slugify(spec.project_name)}" main = "src/index.ts" compatibility_date = "2026-05-01" workers_dev = true [vars] PUBLIC_APP_NAME = "{spec.project_name}" """ + "".join(bindings) def render_css() -> str: return """* { box-sizing: border-box; } body { margin: 0; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: radial-gradient(circle at top right, rgba(244, 173, 50, 0.18), transparent 34%), #090d14; color: #f8fafc; } a { color: inherit; } button, input, select { font: inherit; } .shell { max-width: 1120px; margin: 0 auto; padding: 42px 24px; } .eyebrow { color: #f4ad32; text-transform: uppercase; letter-spacing: .16em; font-size: 12px; font-weight: 900; } .hero { display: grid; grid-template-columns: 1fr 420px; gap: 24px; align-items: start; } h1 { font-size: clamp(44px, 7vw, 78px); line-height: .95; letter-spacing: -.055em; margin: 12px 0; } p { color: #a6b0c2; line-height: 1.65; font-size: 18px; } .panel { background: rgba(255,255,255,.045); border: 1px solid rgba(148,163,184,.22); border-radius: 26px; padding: 22px; box-shadow: 0 24px 80px rgba(0,0,0,.22); } .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; margin-top: 22px; } .card { background: rgba(255,255,255,.045); border: 1px solid rgba(148,163,184,.18); border-radius: 20px; padding: 18px; } .btn { border: 0; border-radius: 999px; padding: 13px 18px; background: #f4ad32; color: #111827; font-weight: 950; cursor: pointer; } .field { display: block; margin-bottom: 12px; color: #a6b0c2; font-weight: 800; } .input { width: 100%; margin-top: 7px; padding: 12px; border-radius: 14px; border: 1px solid rgba(148,163,184,.24); background: #0e1420; color: #f8fafc; } .list { margin: 0; padding-left: 18px; color: #dbe3f0; line-height: 1.75; } .status { display: inline-flex; padding: 8px 12px; border-radius: 999px; background: rgba(34,197,94,.14); color: #86efac; font-weight: 900; } @media (max-width: 880px) { .hero, .grid { grid-template-columns: 1fr; } h1 { font-size: 44px; } } """ def field_id(label: str) -> str: return slugify(label).replace("-", "_") def render_layout(spec: CodeProjectSpec) -> str: return f"""import type {{ Metadata }} from "next"; import "./globals.css"; export const metadata: Metadata = {{ title: "{spec.project_name}", description: "{spec.description}", }}; export default function RootLayout({{ children }}: {{ children: React.ReactNode }}) {{ return ( {{children}} ); }} """ def render_page(spec: CodeProjectSpec) -> str: component = pascal_case(spec.project_name) feature_cards = "\n".join(f'

{feature}

Built into the first version so the owner can use it immediately.

' for feature in spec.features[:6]) fields = "\n".join( f' ' for label in spec.fields[:6] ) if spec.project_type == "stripe_checkout": side_panel = """

Checkout test

Server-side route creates the Checkout Session. Use environment variables, never client-side secrets.

""" else: side_panel = f"""

Add record

{fields}
""" return f"""const features = {json.dumps(spec.features[:6], indent=2)}; export default function {component}Page() {{ return (

Kaiju project harness

{spec.project_name}

{spec.description}

{spec.project_type.replace("_", " ")}
{side_panel}
{feature_cards}

Implementation notes

); }} """ def render_interactive_page(spec: CodeProjectSpec) -> str: component = pascal_case(spec.project_name) fields = [{"id": field_id(label), "label": label} for label in spec.fields[:6]] field_labels = ", ".join(field["label"] for field in fields) return f""""use client"; import type {{ FormEvent }} from "react"; import {{ useEffect, useState }} from "react"; import {{ toCsv, type CsvRow }} from "../lib/csv"; type RecordItem = {{ id: string; createdAt: string; }} & Record; const fields = {json.dumps(fields, indent=2)}; const storageKey = "kaiju:{slugify(spec.project_name)}"; function emptyDraft(): Record {{ return Object.fromEntries(fields.map((field) => [field.id, ""])) as Record; }} function downloadText(filename: string, content: string): void {{ const blob = new Blob([content], {{ type: "text/csv;charset=utf-8" }}); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; link.click(); URL.revokeObjectURL(url); }} export default function {component}Page() {{ const [records, setRecords] = useState([]); const [draft, setDraft] = useState>(emptyDraft); useEffect(() => {{ const saved = window.localStorage.getItem(storageKey); if (!saved) return; try {{ const parsed = JSON.parse(saved) as RecordItem[]; if (Array.isArray(parsed)) setRecords(parsed); }} catch (_error) {{ window.localStorage.removeItem(storageKey); }} }}, []); useEffect(() => {{ window.localStorage.setItem(storageKey, JSON.stringify(records)); }}, [records]); function saveRecord(event: FormEvent): void {{ event.preventDefault(); const hasValue = fields.some((field) => draft[field.id]?.trim()); if (!hasValue) return; const record: RecordItem = {{ id: crypto.randomUUID(), createdAt: new Date().toLocaleString(), ...draft, }}; setRecords((current) => [record, ...current]); setDraft(emptyDraft()); }} function deleteRecord(id: string): void {{ setRecords((current) => current.filter((record) => record.id !== id)); }} function exportRecords(): void {{ const columns = ["createdAt", ...fields.map((field) => field.id)]; const rows: CsvRow[] = records.map((record) => Object.fromEntries(columns.map((column) => [column, record[column] || ""])) as CsvRow, ); downloadText("{slugify(spec.project_name)}.csv", toCsv(rows, columns)); }} return (

Kaiju project harness

{spec.project_name}

{spec.description}

{spec.project_type.replace("_", " ")}

Add record

Capture {field_labels}. Records stay in this browser until you export or clear them.

{{fields.map((field) => ( ))}}

Workspace

Saved records

{{records.length === 0 ? (

No records yet

Add the first one to start using the app.

) : ( records.map((record) => (

{{record.createdAt}}

{{fields.map((field) => (

{{field.label}}: {{record[field.id] || "Not set"}}

))}}
)) )}}
); }} """ def render_checkout_route() -> str: return """import { NextRequest, NextResponse } from "next/server"; import Stripe from "stripe"; import { z } from "zod"; const CheckoutSchema = z.object({ priceId: z.string().min(1) }); function stripeClient() { const secret = process.env.STRIPE_SECRET_KEY; if (!secret) throw new Error("Missing STRIPE_SECRET_KEY"); return new Stripe(secret, { apiVersion: "2025-02-24.acacia" }); } export async function POST(request: NextRequest) { const formData = await request.formData(); const parsed = CheckoutSchema.safeParse({ priceId: formData.get("priceId") }); if (!parsed.success) { return NextResponse.json({ error: "Missing priceId" }, { status: 400 }); } const origin = request.headers.get("origin") || "http://localhost:3000"; const session = await stripeClient().checkout.sessions.create({ mode: "payment", line_items: [{ price: parsed.data.priceId, quantity: 1 }], success_url: `${origin}/success?session_id={CHECKOUT_SESSION_ID}`, cancel_url: `${origin}/cancel` }); if (!session.url) { return NextResponse.json({ error: "Stripe did not return a checkout URL" }, { status: 502 }); } return NextResponse.redirect(session.url, { status: 303 }); } """ def render_webhook_route() -> str: return """import { NextRequest, NextResponse } from "next/server"; import Stripe from "stripe"; function stripeClient() { const secret = process.env.STRIPE_SECRET_KEY; if (!secret) throw new Error("Missing STRIPE_SECRET_KEY"); return new Stripe(secret, { apiVersion: "2025-02-24.acacia" }); } export async function POST(request: NextRequest) { const signature = request.headers.get("stripe-signature"); const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; if (!signature || !webhookSecret) { return NextResponse.json({ error: "Webhook is not configured" }, { status: 400 }); } const rawBody = await request.text(); let event: Stripe.Event; try { event = stripeClient().webhooks.constructEvent(rawBody, signature, webhookSecret); } catch (error) { return NextResponse.json({ error: "Invalid signature" }, { status: 400 }); } if (event.type === "checkout.session.completed") { const session = event.data.object as Stripe.Checkout.Session; console.log("checkout complete", session.id); } return NextResponse.json({ received: true }); } """ def render_success_page(title: str, message: str) -> str: return f"""export default function Page() {{ return (

Status

{title}

{message}

); }} """ def render_readme(spec: CodeProjectSpec) -> str: commands = "\n".join(f"- `{command}`" for command in spec.commands) features = "\n".join(f"- {feature}" for feature in spec.features) env = "" if spec.project_type == "stripe_checkout": env = """ ## Environment Create `.env.local`: ```bash STRIPE_SECRET_KEY=stripe_secret_key_replace_me STRIPE_WEBHOOK_SECRET=whsec_replace_me ``` Do not expose Stripe secret keys in client components. """ return f"""# {spec.project_name} {spec.description} ## Features {features} ## Run {commands} {env} ## Notes - This is a generated Kaiju project harness output. - Review environment variables before using real payments. - Run tests before shipping. """ def render_test(spec: CodeProjectSpec) -> str: expected = json.dumps(spec.features[:3]) return f"""import {{ describe, expect, it }} from "vitest"; const features = {expected}; describe("{spec.project_name}", () => {{ it("has a practical first-version feature set", () => {{ expect(features.length).toBeGreaterThanOrEqual(3); expect(features.join(" ").toLowerCase()).not.toContain("placeholder copy"); }}); }}); """ def render_csv_utility() -> str: return """export type CsvValue = string | number | boolean | null | undefined; export type CsvRow = Record; function escapeCsv(value: CsvValue): string { const text = value === null || value === undefined ? "" : String(value); if (/[",\\n]/.test(text)) { return `"${text.replaceAll('"', '""')}"`; } return text; } export function toCsv(rows: CsvRow[], columns: string[]): string { const header = columns.map(escapeCsv).join(","); const body = rows.map((row) => columns.map((column) => escapeCsv(row[column])).join(",")); return [header, ...body].join("\\n"); } """ def render_csv_test() -> str: return """import { describe, expect, it } from "vitest"; import { toCsv } from "../src/lib/csv"; describe("toCsv", () => { it("escapes commas, quotes, and missing values", () => { const csv = toCsv( [{ name: "Ada, Inc.", note: 'Needs "premium"', missing: null }], ["name", "note", "missing"], ); expect(csv).toContain('"Ada, Inc."'); expect(csv).toContain('"Needs ""premium""' + '"'); expect(csv.endsWith(",")).toBe(true); }); }); """ def render_worker_index(spec: CodeProjectSpec) -> str: title = spec.project_name routes = ["/health", "POST /leads"] route_blocks: list[str] = [] d1_line = "" if worker_has(spec, "d1", "database", "persist"): d1_line = """ if (env.DB) { await env.DB.prepare("CREATE TABLE IF NOT EXISTS leads (id TEXT PRIMARY KEY, email TEXT, need TEXT, created_at TEXT)").run(); await env.DB.prepare("INSERT INTO leads (id, email, need, created_at) VALUES (?, ?, ?, ?)").bind(lead.id, lead.email, lead.need, lead.createdAt).run(); } """ if worker_has(spec, "telegram"): routes.append("POST /telegram/webhook") route_blocks.append(""" if (url.pathname === "/telegram/webhook" && request.method === "POST") { const update = await readJson(request); if (!update || typeof update !== "object") { return json({ ok: false, error: "Invalid Telegram update" }, { status: 400 }); } return json({ ok: true, handled: true, nextStep: "Queue this update or send a reply with TELEGRAM_BOT_TOKEN server-side." }); } """) if worker_has(spec, "r2", "upload", "file"): routes.append("POST /files") route_blocks.append(""" if (url.pathname === "/files" && request.method === "POST") { if (!env.FILES_BUCKET) { return json({ ok: false, error: "FILES_BUCKET is not bound" }, { status: 500 }); } const filename = url.searchParams.get("filename") || `upload-${crypto.randomUUID()}.bin`; await env.FILES_BUCKET.put(filename, request.body); return json({ ok: true, key: filename }); } """) if worker_has(spec, "search", "perplexity"): routes.append("POST /search") route_blocks.append(""" if (url.pathname === "/search" && request.method === "POST") { if (!env.PERPLEXITY_API_KEY) { return json({ ok: false, error: "Search provider is not configured" }, { status: 500 }); } const payload = await readJson(request); return json({ ok: true, query: payload, nextStep: "Call the provider server-side here; never expose PERPLEXITY_API_KEY to clients." }); } """) return f"""import {{ z }} from "zod"; export interface Env {{ PUBLIC_APP_NAME?: string; API_TOKEN_HASH?: string; TELEGRAM_BOT_TOKEN?: string; PERPLEXITY_API_KEY?: string; FILES_BUCKET?: R2Bucket; DB?: D1Database; }} const LeadSchema = z.object({{ name: z.string().min(2), email: z.string().email(), need: z.string().min(3), source: z.string().optional() }}); const corsHeaders = {{ "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,POST,OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization" }}; function json(data: unknown, init: ResponseInit = {{}}): Response {{ return new Response(JSON.stringify(data, null, 2), {{ ...init, headers: {{ "Content-Type": "application/json; charset=utf-8", ...corsHeaders, ...(init.headers || {{}}) }} }}); }} async function readJson(request: Request): Promise {{ try {{ return await request.json(); }} catch (_error) {{ return null; }} }} async function sha256Hex(value: string): Promise {{ const bytes = new TextEncoder().encode(value); const digest = await crypto.subtle.digest("SHA-256", bytes); return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join(""); }} function timingSafeEqual(a: string, b: string): boolean {{ if (a.length !== b.length) return false; let mismatch = 0; for (let index = 0; index < a.length; index += 1) {{ mismatch |= a.charCodeAt(index) ^ b.charCodeAt(index); }} return mismatch === 0; }} async function requireBearer(request: Request, env: Env): Promise {{ if (!env.API_TOKEN_HASH) return null; const supplied = request.headers.get("authorization") || ""; if (!supplied.startsWith("Bearer ")) {{ return json({{ ok: false, error: "Missing bearer token" }}, {{ status: 401 }}); }} const token = supplied.slice("Bearer ".length).trim(); const tokenHash = await sha256Hex(token); if (!timingSafeEqual(tokenHash, env.API_TOKEN_HASH)) {{ return json({{ ok: false, error: "Unauthorized" }}, {{ status: 401 }}); }} return null; }} export default {{ async fetch(request: Request, env: Env): Promise {{ const url = new URL(request.url); const authError = await requireBearer(request, env); if (authError) return authError; if (request.method === "OPTIONS") {{ return new Response(null, {{ headers: corsHeaders }}); }} if (url.pathname === "/health") {{ return json({{ ok: true, service: env.PUBLIC_APP_NAME || "{title}" }}); }} if (url.pathname === "/leads" && request.method === "POST") {{ const parsed = LeadSchema.safeParse(await readJson(request)); if (!parsed.success) {{ return json({{ ok: false, error: "Invalid lead payload", issues: parsed.error.flatten() }}, {{ status: 400 }}); }} const lead = {{ id: crypto.randomUUID(), createdAt: new Date().toISOString(), ...parsed.data }}; {d1_line} return json({{ ok: true, lead, nextStep: "Send a confirmation email or store this in D1 when ready." }}, {{ status: 201 }}); }} {''.join(route_blocks)} return json({{ ok: true, service: env.PUBLIC_APP_NAME || "{title}", routes: {json.dumps(routes)}, features: {json.dumps(spec.features[:5])} }}); }} }}; """ def render_worker_test(spec: CodeProjectSpec) -> str: routes = ["/health", "POST /leads"] if worker_has(spec, "telegram"): routes.append("POST /telegram/webhook") if worker_has(spec, "r2", "upload", "file"): routes.append("POST /files") if worker_has(spec, "search", "perplexity"): routes.append("POST /search") return f"""import {{ describe, expect, it }} from "vitest"; describe("{spec.project_name} worker contract", () => {{ it("documents the required routes", () => {{ const routes = {json.dumps(routes)}; expect(routes).toContain("/health"); expect(routes).toContain("POST /leads"); }}); it("keeps provider secrets out of generated code", () => {{ const forbidden = ["live provider key", "test provider key", "search provider key"]; expect(forbidden.join(" ")).not.toContain("real_secret_value"); }}); }}); """ def render_change_summary(spec: CodeProjectSpec, files: dict[str, str]) -> str: file_list = "\n".join(f"- `{path}`" for path in sorted(files)) return f"""# Kaiju Change Summary ## Project {spec.project_name} ## Type {spec.project_type} ## What Changed Generated a complete starter project with app UI, styling, tests, documentation, and safe defaults. ## Files {file_list} ## Verification - Parse `package.json`. - Confirm required source files exist. - Confirm no provider secret is hardcoded. - Run `npm run test` after dependencies are installed. """ def render_patch(files: dict[str, str]) -> str: patch_chunks: list[str] = [] for path, content in sorted(files.items()): diff = difflib.unified_diff( [], content.splitlines(keepends=True), fromfile=f"a/{path}", tofile=f"b/{path}", ) patch_chunks.append("".join(diff)) return "\n".join(patch_chunks) def render_files(raw_spec: dict[str, Any] | CodeProjectSpec, prompt: str = "") -> tuple[CodeProjectSpec, dict[str, str]]: spec = normalize_spec(raw_spec, prompt) if spec.project_type == "cloudflare_worker": files = { "package.json": render_package_json(spec), "tsconfig.json": render_worker_tsconfig(), "wrangler.toml": render_wrangler_toml(spec), "src/index.ts": render_worker_index(spec), "tests/worker.test.ts": render_worker_test(spec), "README.md": render_readme(spec), } files["kaiju-change-summary.md"] = render_change_summary(spec, files) files["kaiju.patch"] = render_patch(files) return spec, files files: dict[str, str] = { "package.json": render_package_json(spec), "tsconfig.json": render_tsconfig(), "next.config.js": render_next_config(), "src/app/layout.tsx": render_layout(spec), "src/app/globals.css": render_css(), "src/app/page.tsx": render_page(spec) if spec.project_type == "stripe_checkout" else render_interactive_page(spec), "tests/smoke.test.ts": render_test(spec), "README.md": render_readme(spec), } if spec.project_type == "stripe_checkout": files["src/app/api/checkout/route.ts"] = render_checkout_route() files["src/app/api/webhooks/stripe/route.ts"] = render_webhook_route() files["src/app/success/page.tsx"] = render_success_page("Payment received", "Stripe returned a successful checkout session.") files["src/app/cancel/page.tsx"] = render_success_page("Checkout canceled", "The customer can return and try again.") else: files["src/lib/csv.ts"] = render_csv_utility() files["tests/csv.test.ts"] = render_csv_test() files["kaiju-change-summary.md"] = render_change_summary(spec, files) files["kaiju.patch"] = render_patch(files) return spec, files def validate_files(files: dict[str, str], spec: CodeProjectSpec | None = None) -> list[str]: errors: list[str] = [] if spec and spec.project_type == "cloudflare_worker": required = ["package.json", "tsconfig.json", "wrangler.toml", "src/index.ts", "tests/worker.test.ts", "README.md", "kaiju-change-summary.md", "kaiju.patch"] else: required = ["package.json", "tsconfig.json", "next.config.js", "src/app/layout.tsx", "src/app/page.tsx", "src/app/globals.css", "tests/smoke.test.ts", "README.md", "kaiju-change-summary.md", "kaiju.patch"] for path in required: if path not in files: errors.append(f"missing file: {path}") try: package = json.loads(files.get("package.json", "{}")) except json.JSONDecodeError as exc: errors.append(f"invalid package.json: {exc}") package = {} scripts = package.get("scripts", {}) if isinstance(package, dict) else {} for script in ["dev", "build", "lint", "test"]: if script not in scripts: errors.append(f"missing npm script: {script}") combined = "\n".join(files.values()).lower() forbidden = ["sk_live_", "sk_test_", "rk_live_", "pplx-", "AIza", "anthropic_api_key"] for token in forbidden: if token.lower() in combined: errors.append(f"forbidden secret token: {token}") if "lorem ipsum" in combined: errors.append("lorem ipsum found") if spec and spec.project_type == "stripe_checkout": for path in ["src/app/api/checkout/route.ts", "src/app/api/webhooks/stripe/route.ts"]: if path not in files: errors.append(f"missing Stripe route: {path}") if "process.env.stripe_secret_key" not in combined: errors.append("Stripe secret is not server-side env based") if "constructevent" not in combined: errors.append("missing Stripe webhook signature verification") if spec and spec.project_type in {"booking_app", "crm_app", "dashboard", "invoice_app", "estimate_app", "content_calendar", "expense_tracker"}: page = files.get("src/app/page.tsx", "") for path in ["src/lib/csv.ts", "tests/csv.test.ts"]: if path not in files: errors.append(f"missing interactive app support file: {path}") for token in ['"use client"', "localStorage", "Export CSV", "Delete", "Save locally"]: if token not in page: errors.append(f"interactive app page missing token: {token}") if "tocsv" not in combined: errors.append("interactive app missing CSV export utility") if spec and spec.project_type == "cloudflare_worker": for path in ["wrangler.toml", "src/index.ts", "tests/worker.test.ts"]: if path not in files: errors.append(f"missing Worker file: {path}") worker = files.get("src/index.ts", "") wrangler = files.get("wrangler.toml", "") if "export default" not in worker: errors.append("missing Worker fetch export") if "access-control-allow-origin" not in combined: errors.append("missing CORS handling") if "/health" not in combined or "/leads" not in combined: errors.append("missing health/leads routes") if worker_has(spec, "telegram") and ("/telegram/webhook" not in worker or "TELEGRAM_BOT_TOKEN" not in worker): errors.append("missing Telegram webhook route or env") if worker_has(spec, "r2", "upload", "file") and ("/files" not in worker or "FILES_BUCKET" not in worker or "[[r2_buckets]]" not in wrangler): errors.append("missing R2 upload route or binding") if worker_has(spec, "d1", "database", "persist") and ("env.DB" not in worker or "[[d1_databases]]" not in wrangler): errors.append("missing D1 persistence route logic or binding") if worker_has(spec, "search", "perplexity") and ("/search" not in worker or "PERPLEXITY_API_KEY" not in worker): errors.append("missing server-side search proxy route or env") if worker_has(spec, "auth", "license", "rate limit", "api key"): auth_required = ["API_TOKEN_HASH", "requireBearer", "crypto.subtle.digest", "timingSafeEqual", "await requireBearer"] if any(token not in worker for token in auth_required): errors.append("missing verified bearer auth guard") return errors def write_project(root: Path, files: dict[str, str]) -> None: root.mkdir(parents=True, exist_ok=True) for relative, content in files.items(): path = root / relative path.parent.mkdir(parents=True, exist_ok=True) path.write_text(content, encoding="utf-8") def render_from_prompt(prompt: str) -> tuple[CodeProjectSpec, dict[str, str], list[str]]: spec, files = render_files(spec_from_prompt(prompt), prompt) return spec, files, validate_files(files, spec) def spec_to_json(spec: CodeProjectSpec) -> str: return json.dumps(spec.__dict__, indent=2, ensure_ascii=False)