2nth Platform
Code & Infrastructure Review
Comprehensive review of 2nth-skills and 2nth-site β including a full skills.2nth.ai redesign spec with shared auth, membership gating, and activity tracking β plus prioritized Cloudflare optimizations for your paid account.
Architecture Overview
Clean Edge-Native Architecture
Both repos are fully Cloudflare-native from day one β D1, KV, Workers AI, Pages Functions. No Node.js server to manage.
Logical Separation
Skills catalog (public, installable AI context) cleanly separated from the client platform. Skills inform the agents that build for clients.
Missing Async Layer
AI inference runs synchronously in HTTP request handlers. Long-running multi-agent analyses will hit the 30s CPU limit and leave clients hanging.
No Real-time Connections
Agent conversations are request/response. No WebSockets, no streaming. Users submit a brief and wait β no live progress feedback.
2nth-skills
allowed-tools directives to constrain agent capabilities per-skill.
What's working well
name, description, license, metadata, allowed-tools) aligns with the skills CLI spec. The allowed-tools pattern (e.g. Bash(curl:*) Bash(npx:*)) properly constrains tool permissions per-skill β a good security practice.main. Zero build step, deploying pre-built public/. The simplest possible CI/CD β appropriate for a static skills catalog.Issues & Improvements
styles.css and using Cloudflare's edge cache + compression. With a paid plan, you get Brotli compression and can set custom cache rules to cache these aggressively (1 year TTL + cache-busting on deploy)..github/workflows/deploy.yml. While Cloudflare account IDs are technically semi-public, best practice is to move it to a GitHub repo secret alongside the API token to prevent leakage and enable multi-account management.npx skills add imbilawork/2nth-skills@<skill-name>. As skills evolve, users will get the latest version implicitly. Consider adding semantic version tags to GitHub releases so users can pin to a version (@sagex3-ai@1.2.0) β especially important as clients rely on these for production builds.2nth-platform skill that would let AI agents understand the platform's own API, D1 schema, token model, and agent patterns. Adding this would enable agents to help clients build platform integrations and would be a strong dogfooding signal.@cf/baai/bge-base-en-v1.5) and query Vectorize from the catalog site.2nth-site
Architecture Strengths
Typed D1 Query Layer
All D1 queries live in src/db.ts with TypeScript wrappers. No raw SQL scattered in route handlers. db.batch() used for atomic token deduction β correct approach.
Auth Middleware Pattern
Auth context injected once in _middleware.ts global, available via context.data everywhere. No repetitive session checks in route handlers.
Web Crypto JWT (No Library)
Custom HMAC-SHA256 JWT using Web Crypto API β correct for the edge environment where Node crypto is not available. Avoids a bundled dependency.
Built-in CRM Layer
CRM stages, lead scoring, activity logs, and nurture queues built into D1 β avoids an external CRM dependency and keeps client data on Cloudflare infrastructure.
Issues Requiring Attention
POST /api/projects/:id/analyse endpoint runs 5 AI agent calls synchronously within a single HTTP request. Cloudflare Pages Functions have a 30-second CPU time limit. Multi-agent analysis chains will timeout on complex briefs. Fix: move AI inference to Cloudflare Queues β Workers.analyse.ts, AI response is parsed with /\{[\s\S]*\}/ regex then JSON.parse. If Llama 3.3 adds preamble text, wraps in markdown code fences, or returns partial output, this silently fails with a 500 error. Use structured outputs (Workers AI supports JSON schema enforcement) or a robust extraction function.text.length / 4 is a character-count heuristic that works for ASCII English but will undercount significantly for code-heavy briefs (symbols, brackets), non-Latin scripts, or structured data. Consider using a tiktoken-compatible WASM module or accepting a 1.5x safety margin by using text.length / 3 as an alternative.INSERT OR IGNORE in 0001_initial.sql. Updating a system prompt requires a new migration. Consider storing system prompts in KV (versioned by key) or a prompt_versions table with an active flag β enabling A/B testing and rollbacks without schema changes.verify.ts) and Resend email sends have no retry logic. If Paystack returns a 500 on verification, the webhook will succeed but the token credit will fail silently. Add exponential backoff retry with Queues β queue the credit operation, retry on failure.auth-bar.js, app-switcher.js, browse-gate.js, tier-gate.js). This works now but becomes painful as the platform grows. Consider migrating to a lightweight framework (Solid.js, Preact) compiled to Pages β still fully static, but with component reuse.Integration Quality
| Integration Point | Current State | Quality | Recommendation |
|---|---|---|---|
| Skills β Platform Agents | Implicit β agents in 2nth-site would use skills CLI to load context when building | Informal | Formalize: agents in 2nth-site should auto-suggest relevant SKILL.md installs based on project brief content |
| Deployment Consistency | Both use Wrangler + Cloudflare Pages, same compatibility date format | Good | Consolidate to a monorepo or at minimum share a wrangler.toml conventions document |
| Domain routing | skills.2nth.ai and 2nth.ai as separate Pages projects |
Good | Use Cloudflare DNS + Pages custom domains β already the right pattern |
| Shared auth | No cross-domain SSO between skills catalog and platform | N/A | If skills catalog gets authenticated features, use Cloudflare Access with the same JWT secret to share sessions |
| CI/CD pipeline | GitHub Actions in 2nth-skills; npm scripts in 2nth-site | Inconsistent | Migrate both to Cloudflare Pages CI (built-in, no separate GitHub Actions token management) |
| CORS allowed origins | skills.2nth.ai listed in 2nth-site's CORS config |
Good | Already planned for β future skills catalog API calls to platform will work |
skills.2nth.ai β Redesign Brief
Current vs Target State
| Dimension | Current State | Target State |
|---|---|---|
| Auth | None β fully public static site | Reads 2nth_session cookie from 2nth.ai via cross-domain API call. Shows personalized header if authenticated. |
| Install button | Public β anyone can see the install command | Gated by plan tier. Explorer: all free skills. Starter+: premium skills. Copy command visible only to logged-in users. |
| Skill catalog | Static HTML, no categorization | Filterable by domain, tier, compatibility. Personalized: highlight skills relevant to user's active projects. |
| Tracking | None | Every skill view, install click, and search tracked to the user's activity_log in 2nth-site D1. Feeds CRM lead scoring. |
| Membership context | None | Shows current plan badge, token balance, and an upgrade CTA if the user is on Explorer browsing a Starter-tier skill. |
| Navigation | Isolated from 2nth.ai | Shared nav component with links back to Dashboard, Projects, Account. Sub-nav specific to Skills. |
Proposed Page Structure
Membership Gating
Proposed Tier Structure for Skills
| Plan | Skills Access | Install Limit | Features |
|---|---|---|---|
| Explorer (Free) | Free-tier skills only Shopify basics, ERPNext intro, Cloudflare intro |
3 skills max active | Browse catalog, view skill docs, limited installs |
| Starter | All free + Starter skills Full Shopify, ERPNext, Sage X3 basics |
10 skills max active | + Skill history, usage analytics, email notifications on updates |
| Designer | All skills + Designer exclusives Advanced Sage X3, full Cloudflare, 2nth-platform |
Unlimited | + Custom skill requests, early access, semantic search |
| Builder | All skills + private client skills Custom-built skills for Builder clients |
Unlimited + private repo | + Private skills, custom SKILL.md authoring, SLA |
SKILL.md Metadata β Add Tier Field
Add a tier field to the YAML frontmatter so the catalog can enforce gating server-side:
--- name: sagex3-ai description: Sage X3 ERP GraphQL integration patterns for AI coding agents license: MIT tier: starter # free | starter | designer | builder metadata: domain: erp systems: [sage-x3] category: enterprise-erp allowed-tools: Bash(curl:*) Bash(npx:*) ---
Gating Logic β Client-Side + Edge Enforcement
// skills.2nth.ai β tier check before showing install command
const TIER_ORDER = ['explorer', 'starter', 'designer', 'builder'];
function canInstall(userPlan: string, skillTier: string): boolean {
return TIER_ORDER.indexOf(userPlan) >= TIER_ORDER.indexOf(skillTier);
}
function renderInstallPanel(skill, user) {
if (!user) {
return `<div class="install-gate">
<a href="https://2nth.ai/join.html?return=${location.href}">Log in to install</a>
</div>`;
}
if (!canInstall(user.plan, skill.tier)) {
return `<div class="install-gate upgrade">
<p>This skill requires <strong>${skill.tier}</strong> plan</p>
<a href="https://2nth.ai/bill.html">Upgrade β from R1,850/mo</a>
</div>`;
}
return `<div class="install-command">
<code>npx skills add imbilawork/2nth-skills@${skill.name}</code>
<button onclick="copyInstall('${skill.name}')">Copy</button>
</div>`;
}
Activity Tracking
Events to Track
| Event | Trigger | CRM Impact | Data to Log |
|---|---|---|---|
skill_viewed |
User opens a skill detail page | +1 lead score if not yet a client | skill_name, referrer, plan |
skill_install_copied |
User clicks "Copy" on install command | +5 lead score, move to demo_user stage |
skill_name, plan, timestamp |
skill_upgrade_prompted |
User hits a gated skill above their tier | Flag for nurture email | skill_name, user_plan, required_tier |
skill_search |
User searches the catalog | Enrich lead_interests | query, results_count, clicked |
skill_catalog_visited |
First visit to skills.2nth.ai | If no account: create anon_identity | referrer, utm_source, utm_campaign |
Tracking Implementation β POST to 2nth.ai API
Add a lightweight tracking endpoint to 2nth-site, then fire it from skills.2nth.ai with fetch(..., {credentials: 'include', keepalive: true}):
// 2nth-site: functions/api/track/skill.ts (new endpoint)
export async function onRequestPost({ request, env, data }) {
const { event, skill_name, metadata } = await request.json();
const user = data.user; // injected by global _middleware.ts
if (user) {
// Log to activity_log table (already exists in D1)
await env.DB.prepare(
`INSERT INTO activity_log (user_id, action, metadata, created_at)
VALUES (?, ?, ?, ?)`
).bind(user.id, event, JSON.stringify({ skill_name, ...metadata }), Date.now()).run();
// Update lead score for high-intent events
if (event === 'skill_install_copied') {
await env.DB.prepare(
`UPDATE users SET lead_score = lead_score + 5 WHERE id = ?`
).bind(user.id).run();
}
} else {
// Anonymous: log to anon_identities fingerprint
const fp = getFingerprint(request);
await env.DB.prepare(
`INSERT OR IGNORE INTO anon_identities (fingerprint, first_seen, metadata)
VALUES (?, ?, ?)`
).bind(fp, Date.now(), JSON.stringify({ referrer: request.headers.get('Referer') })).run();
}
return new Response(null, { status: 204 });
}
// skills.2nth.ai β fire tracking events
function track(event, data = {}) {
fetch('https://2nth.ai/api/track/skill', {
method: 'POST',
credentials: 'include',
keepalive: true, // survives page unload
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ event, ...data })
}).catch(() => {}); // fire-and-forget, never block UI
}
Cloudflare Web Analytics β Custom Events
Layer Cloudflare Web Analytics custom events on top of the API tracking for funnel visibility:
// Fire both: API tracking (to D1) + CF Analytics (to CF dashboard)
function track(event, data = {}) {
// 1. Server-side: logs to D1 activity_log + CRM
fetch('https://2nth.ai/api/track/skill', { method: 'POST', credentials: 'include',
keepalive: true, body: JSON.stringify({ event, ...data }) }).catch(() => {});
// 2. Client-side: CF Web Analytics custom event (appears in CF dashboard)
if (typeof window.cfAnalytics !== 'undefined') {
window.cfAnalytics.pushEvent({ type: event, ...data });
}
}
Personalization Based on Tracking
Relevant Skills by Project
When a user has active projects in D1, the skills catalog highlights skills matching their project domains. "You're building an ERPNext integration β here are relevant skills."
Upgrade Prompt Emails
When skill_upgrade_prompted is logged, add user to nurture_queue with context. Resend email: "You browsed [skill] β it's included in Starter at R1,850/mo."
Skill Interest in Lead Profile
Store the domains of viewed/installed skills in lead_interests column. Sales context: "This lead has installed Shopify and ERPNext skills β likely a commerce + ERP integration project."
Security Findings
POST /api/billing/webhook should verify the x-paystack-signature HMAC header before processing. Without this, any actor can POST fake payment events and fraudulently credit tokens. Paystack signs all webhooks with your secret key β validate it with crypto.subtle.verify()./api/admin/* routes check user.is_admin from D1. If there's an SQL injection or a privilege escalation bug anywhere in the auth layer, attackers gain full admin access. Back this up with a secondary check β a fixed admin email allowlist checked server-side, or Cloudflare Access protecting /api/admin/* with a service token.pk_live_ Paystack publishable key is committed in wrangler.toml. While publishable keys are designed to be client-facing and cannot authorize charges, exposing the live key links your payment processor identity to the repo. Move it to a wrangler var (non-secret env var set via dashboard) or accept it as a build-time env var in CI.HttpOnly; Secure; SameSite=Lax, JWT uses HMAC-SHA256 with Web Crypto (no library vulnerabilities), anonymous user fingerprinting is IP+UA (no persistent tracking).
Quick Wins β Days
hostname eq "skills.2nth.ai" β Edge TTL: 1 year, Browser TTL: 1 day. Bust the cache on each Pages deploy via a deploy hook. With paid plan, you get Cache Rules instead of Page Rules (more powerful, per-path control).// wrangler.toml β Cache Rules are set in CF Dashboard > Caching > Cache Rules // Or via Terraform / CF API for the paid plan
functions/api/billing/webhook.ts before any processing logic.// functions/api/billing/webhook.ts
const hash = await crypto.subtle.importKey(
'raw',
new TextEncoder().encode(env.PAYSTACK_SECRET_KEY),
{ name: 'HMAC', hash: 'SHA-512' },
false, ['sign']
);
const sig = await crypto.subtle.sign('HMAC', hash, new TextEncoder().encode(body));
const expected = Array.from(new Uint8Array(sig))
.map(b => b.toString(16).padStart(2, '0')).join('');
if (expected !== req.headers.get('x-paystack-signature')) {
return new Response('Unauthorized', { status: 401 });
}
/api/admin/* on 2nth.ai requiring a service token. Admin API calls from your dashboard/tools include the CF-Access-Client-Id header. External callers are rejected at the edge before hitting your Functions β zero code change needed.data-cf-beacon='{token: "..."}' to each page β or inject via Cloudflare's automatic injection in Pages settings.<script defer src='https://static.cloudflareinsights.com/beacon.min.js'
data-cf-beacon='{"token": "YOUR_TOKEN"}'></script>
2nth.ai with a default action of Block. Your API routes will get automatic protection against SQL injection, XSS, and common attack patterns β zero code change, edge-level protection.GET /api/agents, GET /api/billing/plans, and GET /api/tokens/balance are read-heavy. D1's time-travel allows reads from the closest replica globally. Set experimental_d1_localMode: true in wrangler for local dev, and ensure read queries use db.prepare().all() (already done) β Cloudflare handles replica routing automatically on paid D1.src/middleware/rate-limit.ts) uses KV reads/writes on every request. The paid plan's Rate Limiting rules run at the edge before your Worker executes β zero KV cost, sub-millisecond evaluation. Set up a rule: path: /api/auth/send-magic-link, limit: 1 per 60s per IP.Medium Term β Weeks
POST /api/projects/:id/analyse handler enqueues a job; a separate Consumer Worker runs the 5-agent chain, updates D1, and sends the email. Clients get an immediate 202 response and poll for status (or use a webhook/email notification, which already exists).// wrangler.toml additions
[[queues.producers]]
queue = "brief-analysis"
binding = "ANALYSIS_QUEUE"
[[queues.consumers]]
queue = "brief-analysis"
max_batch_size = 1
max_retries = 3
dead_letter_queue = "brief-analysis-dlq"
// functions/api/projects/[id]/analyse.ts
await env.ANALYSIS_QUEUE.send({ projectId: id, userId: user.id });
return new Response(JSON.stringify({ status: 'queued' }), { status: 202 });
stream: true) β pipe the ReadableStream through the DO to the client's WebSocket.// Agent DO: maintains WebSocket connection, streams Workers AI output
export class AgentSession {
async fetch(req: Request) {
const { 0: client, 1: server } = new WebSocketPair();
this.ctx.acceptWebSocket(server);
const stream = await this.env.AI.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
messages, stream: true
});
// Pipe stream chunks to WebSocket
return new Response(null, { status: 101, webSocket: client });
}
}
response_format parameter with JSON schema enforcement. Replace the regex extraction in analyse.ts with a typed schema that guarantees structured output β no regex, no 500 errors on malformed LLM output.const result = await env.AI.run('@cf/meta/llama-3.3-70b-instruct-fp8-fast', {
messages,
response_format: {
type: 'json_schema',
json_schema: {
name: 'brief_analysis',
schema: {
type: 'object',
properties: {
design: { type: 'object' },
software: { type: 'object' },
hardware: { type: 'object' },
robotics: { type: 'object' },
total_tokens: { type: 'number' }
},
required: ['design', 'software', 'hardware', 'robotics', 'total_tokens']
}
}
}
});
// wrangler.toml
[[r2_buckets]]
binding = "BRIEFS"
bucket_name = "2nth-briefs"
// Store brief attachment
const key = `briefs/${projectId}/${filename}`;
await env.BRIEFS.put(key, file.stream(), {
httpMetadata: { contentType: file.type }
});
await db.updateProjectBriefAttachment(projectId, key);
// Store: PROMPTS:brief-analyst:v3 = "You are..."
// Active version pointer: PROMPTS:brief-analyst:current = "v3"
const version = await env.PROMPTS.get(`PROMPTS:${agentSlug}:current`);
const prompt = await env.PROMPTS.get(`PROMPTS:${agentSlug}:${version}`);
Strategic β Months
Vectorize β Semantic Skills Search
As 2nth-skills grows to 20+ skills, embed SKILL.md content at deploy time using @cf/baai/bge-base-en-v1.5 (free via Workers AI). Clients can search "I need ERP integration for furniture manufacturing" and get the right skill β not just by name.
Cloudflare Images β Client Brief Assets
When clients attach brand guidelines, design assets, or reference images to briefs, Cloudflare Images provides transformation, optimization, and delivery. Use the Images API for responsive variants β /cdn-cgi/image/width=800,format=webp/r2/key.
Workers AI Fine-Tuning (LoRA)
As 2nth accumulates successful project scopes and client briefs, fine-tune the brief-analyst agent using LoRA adapters on Workers AI. Your platform's successful project outcomes become training data for a domain-specific model β a compounding competitive advantage.
Hyperdrive β External Database Connections
When clients have their own Postgres databases (e.g., ERPNext self-hosted), Cloudflare Hyperdrive allows Workers to connect to external Postgres with connection pooling at the edge. Relevant for the ERPNext and Sage X3 skills β agents can query client DBs directly during build.
Cloudflare for Teams (Zero Trust)
As 2nth grows a team, Cloudflare Zero Trust (Access + Gateway) replaces VPN for internal tool access. The admin panel at /admin.html becomes an Access-protected app requiring team SSO β not just an is_admin flag in D1.
Cloudflare Browser Rendering
For generating PDF concept documents from the AI's structured output, Browser Rendering (a paid feature) allows Workers to render HTML to PDF at the edge β no external service like Puppeteer Cloud. Generate client-ready concept PDFs and store in R2.
Product Recommendations
Async message queue for Workers. Solves the synchronous AI inference timeout problem.
- Decouple brief analysis from HTTP request lifecycle
- Retry failed AI calls with exponential backoff (up to 3 retries)
- Dead-letter queue for failed analysis jobs
- Queue Paystack credit operations for reliability
- $0.40 per million messages β near-free at current scale
S3-compatible object storage with zero egress fees. Natural home for project assets.
- Store client brief attachments (images, PDFs, specs)
- Store AI-generated concept documents and outputs
- Store compiled Tailwind CSS (cache-bust on each deploy)
- $0.015/GB/month storage, $0 egress β S3 egress costs $0.09/GB
- Public buckets via
r2.devsubdomain or custom domain
Stateful, globally unique Worker instances. Enable real-time agent interactions.
- WebSocket connections for streaming AI output to clients
- Maintain conversation history in-memory during a session
- Per-project state (one DO per project = isolated conversation)
- Coordinate multi-agent pipelines without polling
- $0.15 per million requests, $12.50 per million GB-seconds
Identity-aware proxy. Protect admin routes and internal tools without code changes.
- Protect
/api/admin/*with a service token policy - Protect
/admin.htmlwith team SSO (Google/GitHub) - Free for up to 50 users on Zero Trust free tier
- Audit logs for all admin access β compliance-ready
- Replace the KV-based rate limiter with Access policies
Already in use β but unlocking additional capabilities with paid plan.
- Structured outputs (JSON schema) β replace regex parsing
- Streaming responses β pipe to Durable Object WebSockets
- Embedding models for Vectorize indexing (
bge-base-en-v1.5) - LoRA fine-tuning for custom brief-analyst specialization
- Consider
@cf/google/gemma-7b-itfor lighter tasks (faster)
Already in use β paid tier removes row limits and adds global read replicas.
- 10B row reads/month (vs 5M free tier)
- 100M row writes/month (vs 100K free tier)
- Time Travel: point-in-time recovery up to 30 days
- Global read replicas β queries routed to closest replica
- Add index on
users.email,projects.user_id,token_transactions.user_id
Vector database for semantic search. Pair with Workers AI embeddings.
- Index SKILL.md content for semantic skill discovery
- Enable "find a skill for my use case" in the catalog
- Index project briefs for similarity matching (suggest past wins)
- $0.04 per million vectors queried β minimal cost
- Deploy embed pipeline as a Queue consumer on skill deploys
Edge-level security rules that run before your Workers. Zero latency impact.
- Cloudflare Managed Ruleset (OWASP + CF rules) β enable immediately
- Rate limit: 1 magic link per IP per 60s (replaces KV middleware)
- Rate limit: 10 agent messages per user per minute
- Bot Fight Mode β prevent credential stuffing on auth routes
- Custom rules: block non-SA IPs from payment routes (reduce fraud)
Privacy-first analytics. No cookies, GDPR-safe, real-time data.
- Add beacon to all pages (automatic via Pages settings)
- Track funnel: visit β magic link β verify β first brief β checkout
- Custom events: track skill installs and agent interactions
- Web Vitals (LCP, CLS, FID) for both sites
- Zero cost, no sampling on paid plan
Route inbound email to Workers or external addresses. Available on paid plan.
- Route
partners@2nth.aito a Worker that auto-creates a D1 record - Route
support@2nth.aito your team inbox - Reduce reliance on Resend for all outbound β use Email Routing for inbound
- Workers Email Sending (beta) β send transactional email from Workers
Transform, optimize, and serve images at the edge. Pairs with R2.
- Serve client brand assets with automatic WebP/AVIF conversion
- Resize brief attachments to appropriate dimensions on-the-fly
- Polish: auto-optimize images on both sites (2nth.ai + skills.2nth.ai)
- $5/month for first 100K images, $1 per 1K thereafter
Connect Workers to external Postgres databases with connection pooling.
- ERPNext and Sage X3 skills could query client external DBs directly
- Relevant when building integrations that need live ERP data access
- Workers establish fast, pooled connections to Postgres from the edge
- Reduces need for client-managed API middleware
Implementation Roadmap
| Week | Task | Effort | Impact | Cloudflare Product |
|---|---|---|---|---|
| Week 1 | Fix Paystack webhook HMAC verification | 1 hr | Revenue Security | β |
| Enable WAF Managed Rules on 2nth.ai | 1 hr | Security | WAF | |
| Add Cloudflare Access policy on /api/admin/* | 30 min | Security | Access | |
| Enable Web Analytics on both domains | 30 min | Visibility | Web Analytics | |
| Week 2 | Migrate GitHub Actions CI to Cloudflare Pages CI | 1 hr | DevEx | Pages CI |
| Replace KV rate limiter with CF Rate Limiting rules | 1 hr | Cost + Perf | Rate Limiting | |
| Add Cache Rules for skills.2nth.ai | 2 hrs | Performance | Cache Rules | |
| Fix Workers AI structured output (replace regex parsing) | half day | Reliability | Workers AI | |
| Week 3 | Implement Cloudflare Queues for async AI analysis | 1β2 days | Reliability | Queues |
| Move agent prompts from D1 seeds to KV with versioning | half day | Maintainability | KV | |
| Week 4 | Provision R2 bucket for project assets | 1β2 days | Scalability | R2 |
| Add D1 indexes on hot query paths | 2 hrs | Performance | D1 | |
| Month 2 | Durable Objects for agent WebSocket streaming | 3β5 days | Product UX | Durable Objects |
| Vectorize + Workers AI embeddings for skill search | 2 days | Product | Vectorize | |
| Month 3+ | Workers AI LoRA fine-tuning on accumulated brief data | 1 week | Competitive | Workers AI (LoRA) |
| Browser Rendering for PDF concept generation | 2 days | Product | Browser Rendering |
Critical path summary
/api/billing/webhook and receive token credits without paying. Fix before any other work.