import Link from 'next/link'; import { AlertTriangle, ArrowRight, Check } from 'lucide-react'; import { Badge } from '@/components/ui/Badge'; import { buttonVariants } from '@/components/ui/Button'; import { Reveal } from '@/components/effects/Reveal'; import { MarketingPage, PageHeader } from '@/components/landing/MarketingSection'; import { api } from '@/lib/api'; import { formatPrice, formatQuota } from '@/lib/billing-format'; import { RECOMMENDED_PLAN, SUBSCRIPTION_PLANS } from '@/lib/constants'; import { BILLING_LIVE, BILLING_PRERELEASE_NOTICE } from '@/lib/legal'; import { cn } from '@/lib/cn'; import { buildPageMetadata } from '@/lib/seo/metadata'; import type { PlanPublicListing } from '@/types'; export const metadata = buildPageMetadata({ title: 'Pricing', description: 'Three paid plans. Subscribe to run tasks — bring your own API keys or use the local model.', path: '/pricing', }); // Prices are read from the backend, the one source of truth, rather than baked // into the build — so a price change can never ship a stale page. export const dynamic = 'force-dynamic'; export default async function PricingPage() { let plans: PlanPublicListing[] = []; let unreachable = false; try { plans = await api.getPublicPlans(); } catch { unreachable = true; } const priced = new Map(plans.map((plan) => [plan.plan, plan])); return ( {!BILLING_LIVE && (

{BILLING_PRERELEASE_NOTICE}

)} {unreachable ? (

> Live pricing is unreachable right now. Please try again shortly.

) : (
{SUBSCRIPTION_PLANS.map((copy, i) => { const pricing = priced.get(copy.plan); if (!pricing) return undefined; const recommended = copy.plan === RECOMMENDED_PLAN; return (
{/* Fixed height so the badge on the recommended plan does not push its title and price out of line with its neighbours. */}

{copy.name}

{recommended && Popular}

{formatPrice(pricing.price_cents, pricing.currency)}

/ mo

{formatQuota(pricing.quota_tokens)}

{copy.tagline}

    {copy.features.map((feature) => (
  • {feature}
  • ))}
{/* Public page, so no admin exemption here: paid plans stay parked for every visitor until BILLING_LIVE flips. Free keeps a live CTA so the page always has one real action. */} {copy.plan === 'free' || BILLING_LIVE ? ( {copy.plan === 'free' ? 'Start free' : 'Get started'} ) : ( Coming soon )}
); })}
)}

Quota is measured in tokens across every agent in a task — the orchestrator, the planner, each subagent and the reviewer. Tasks that fail or time out still account for what they spent. When the quota runs out, new tasks are refused until the period rolls over.

See how a task runs
); }