Files
hanzo.ai/app/(marketing)/agents/view.tsx

313 lines
17 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import type { ReactNode } from 'react'
import { Bot, Building2, KeyRound, Terminal } from 'lucide-react'
import { Box, Text, XStack, YStack } from '@/components/ui'
import { Faq, Line } from '@hanzo/ui/marketing'
import { Take } from '@hanzo/app/copy'
import { Page, PageHero, Section, CardGrid, Prose, Cta } from '@/components/marketing/page-kit'
import { FaqJsonLd } from '@/components/marketing/FaqJsonLd'
import { Inline } from '@/components/api/state'
import { Price } from '@/components/api/rate'
import { Surface } from '@/components/product/Surface'
import { getPrimitive, getPrimitiveCategory } from '@/lib/data/cloud-primitives'
import { rate, type Plan } from '@/lib/api/read'
import { ready, usePlans, usePricing } from '@/lib/api/use'
import { dollars } from '@/lib/tally'
import { LIVE, PITCH, PROMPT, START } from '@/lib/agents'
import { useI18n } from '@hanzo/i18n/react'
/** Where a person books time with Hanzo. */
const CALL = 'https://cal.com/hanzo'
type Samples = Record<'headless' | 'key' | 'call' | 'mcp' | 'kai' | 'harness' | 'campaign', ReactNode>
/**
* The cheapest monthly price a person or a team can buy, in dollars, from the
* plan authority's rows: the floor the copy says plans start at. Null when no
* row sells one.
*/
function floor(rows: Plan[]): number | null {
const sold = rows
.filter((p) => (p.category === 'personal' || p.category === 'team') && !p.contactSales)
.map((p) => p.priceMonthly ?? 0)
.filter((n) => n > 0)
return sold.length ? Math.min(...sold) : null
}
/** A sample under the step it belongs to. The code is the server's (components/code). */
function Step({ n, title, children, sample }: { n: number; title: string; children: ReactNode; sample: ReactNode }) {
return (
<YStack gap="$3" mb="$7">
<XStack gap="$3" items="baseline">
<Text fontFamily="$mono" fontSize="$2" color="$soft">
{n}
</Text>
<Text render="h3" fontSize="$5" fontWeight="500" color="$ink">
{title}
</Text>
</XStack>
<Text render="p" fontSize="$3" lineHeight="$4" color="$soft" maxW={720}>
{children}
</Text>
<Box rounded="var(--radius-md)" borderWidth={1} borderColor="var(--border)" overflow="hidden">
{sample}
</Box>
</YStack>
)
}
/** One line of the offer: what it is, then what it costs. */
function Term({ name, children }: { name: string; children: ReactNode }) {
return (
<XStack
flexDirection="column"
gap="$1"
py="$3"
borderTopWidth={1}
borderColor="var(--border)"
$sm={{ flexDirection: 'row', gap: '$5' }}
>
<Text fontSize="$3" fontWeight="500" color="$ink" $sm={{ width: 180, shrink: 0 }}>
{name}
</Text>
<Text render="p" fontSize="$3" lineHeight="$4" color="$soft" flex={1}>
{children}
</Text>
</XStack>
)
}
/** The Agents product as the catalog states it; the console the hero draws. */
const AGENT = getPrimitive('agents')
const RAIL = getPrimitiveCategory('agents')?.id
export default function AgentsPage({ samples }: { samples: Samples }) {
const { t } = useI18n()
const plans = usePlans()
const pricing = usePricing()
// The two figures the cost answer names, once the authorities answer; until
// then (and in the exported HTML) it says where they are read instead.
const rows = ready(plans)
const doc = ready(pricing)
const from = rows ? floor(rows) : null
const kai = doc ? rate(doc, 'kai')?.input : undefined
const FAQ: readonly { q: string; a: ReactNode }[] = [
{
q: t('What is free?'),
a: t('Signing up, the API key and a small free allowance to try it. GET /v1/billing/tier, with your key, says what your account allows and what is left, and every answer carries X-RateLimit-Remaining. A person on the Free plan gets 50 free requests a day; the request after the last answers 429 until the next UTC midnight.'),
},
{
q: t('What does it cost after that?'),
a:
from && kai != null
? t('You pay as you go from the balance you top up: each call is billed at its model’s rate in GET /v1/pricing. Kai is {kai} per million input tokens, and its output is free. Plans for people start at {from} a month, and GET /v1/billing/plans lists every one.', { kai: dollars(kai), from: dollars(from) })
: t('You pay as you go from the balance you top up: each call is billed at its model’s rate in GET /v1/pricing, and GET /v1/billing/plans lists every plan for people with its price.'),
},
{
q: t('Can an agent sign up without a person?'),
a: t('Yes. With a wallet it needs no inbox and no person: it signs a one-time challenge from hanzo.id, a first-seen wallet gets its own account and organization, and it mints a key with the token it gets back. The whole flow is the script above. An agent with an inbox can sign up by email code instead, and one with neither has its person approve a device code once.'),
},
{
q: t('How does an agent pay?'),
a: t('Over the API in stablecoins: GET /v1/billing/crypto/options lists the chains (today Base, Ethereum and Solana) and the tokens (USDC and USDT) the rail accepts, POST /v1/billing/crypto/deposit returns the address to send to, and GET /v1/billing/crypto/deposit/{id} says when it is credited. Cards, Apple Pay and Google Pay go through the checkout at hanzo.ai/pay, which needs a browser.'),
},
{
q: t('How do I keep an agent from overspending?'),
a: t('Give each agent its own key with a monthly budget in cents and a rate in requests a minute: past the budget a call answers 402, past the rate 429. A session can carry its own cap, set with POST /v1/agent/sessions/{id}/budget, and a session that reaches it pauses until the cap is raised.'),
},
{
q: t('Which tools can my agent use?'),
a: t('The Hanzo MCP server, @hanzo/mcp, runs over stdio from one npx line in Claude Code, Codex, Cursor and Zed. It carries the Kai decision tools beside files, shell, git, code search and the browser.'),
},
]
return (
<Page>
<PageHero
eyebrow={t('For agents')}
icon={Bot}
visual={AGENT && RAIL ? <Surface name={AGENT.title} api={AGENT.api} rail={RAIL} here="agents" /> : undefined}
title={t('Hanzo for agents: free to start, pay as you go')}
lede={t(PITCH)}
>
<XStack gap="$3" flexWrap="wrap">
<Cta href="#headless" icon={Terminal}>
{t('Sign up headless')}
</Cta>
<Cta href={CALL}>{t('Book a call')}</Cta>
</XStack>
<Text render="p" mt="$4" fontSize="$2" color="$soft">
{t('Hanzo has built business software since 2014 for hundreds of venture-funded startups, two of which went public.')}{' '}
<a href="/about">{t('Our track record')}</a>
</Text>
</PageHero>
<Section title={t('What your agent gets, and what it costs')} lede={t('One key opens https://api.hanzo.ai/v1: every model in the catalog, Kai decisions and agents. The figures are read from the API when this page opens.')}>
<YStack borderBottomWidth={1} borderColor="var(--border)">
<Term name={t('Sign-up')}>{t('Free, with a wallet. No inbox, no card, no person.')}</Term>
<Term name={t('API key')}>{t('Free, minted over the API, each with its own monthly budget and rate.')}</Term>
<Term name={t('To try it')}>
{t('A small free allowance on the new account. With your key, GET {tier} says what it allows and what is left.', { tier: LIVE.tier })}
</Term>
<Term name={t('Pay as you go')}>
{t('Each call is billed from your balance at its model’s rate in GET /v1/pricing. Kai decisions:')} <Price id="kai" side="input" />{' '}
{t('per million input tokens, output free.')}
</Term>
<Term name={t('Top up')}>
{t('USDC or USDT over the API, to raise your limits. A card at hanzo.ai/pay or a bank wire works too, for a person.')}
</Term>
<Term name={t('Plans for people')}>
{t('From')} <Inline reading={plans}>{(rows) => (floor(rows) == null ? '—' : dollars(floor(rows)!))}</Inline>{' '}
{t('a month; every plan and price at GET {plans}.', { plans: LIVE.plans })}
</Term>
</YStack>
</Section>
<YStack id="headless">
<Section title={t('Headless: sign up, get a key, top up')} lede={t('The whole path with no person. Every step is one request, and the script below makes them all; it was run as printed against production.')}>
<Prose>
<ol>
<li>{t('Sign up with a wallet: GET hanzo.id/v1/iam/web3/nonce, sign the message with EIP-191, POST hanzo.id/v1/iam/web3/verify, exchange the code at hanzo.id/v1/iam/oauth/token. A first-seen wallet gets its own account and organization.')}</li>
<li>{t('Get a key: POST /v1/account/keys with that token. It is shown once.')}</li>
<li>{t('Read your allowance: GET /v1/billing/tier with the key.')}</li>
<li>{t('Top up: GET /v1/billing/crypto/options, POST /v1/billing/crypto/deposit, send to the address it returns, then GET /v1/billing/crypto/deposit/{id} until it says succeeded.')}</li>
<li>{t('Call: POST /v1/chat/completions, and pay as you go.')}</li>
</ol>
</Prose>
<Box mt="$4" rounded="var(--radius-md)" borderWidth={1} borderColor="var(--border)" overflow="hidden">
{samples.headless}
</Box>
<Text render="p" mt="$4" fontSize="$3" color="$soft" maxW={720}>
{t('Keep the wallet key and the API key in your secret store: the wallet is the account, and the API key is shown once. A deposit address lasts 24 hours and is credited when the chain confirms; send only the token and chain it names.')}
</Text>
</Section>
</YStack>
<Section title={t('Paste this into your agent')} lede={t('What Hanzo is, the base URL, how to discover the API and the headless path, in one block an agent keeps in its context.')}>
<Box overflow="hidden" rounded="var(--radius-2xl)" borderWidth={1} borderColor="var(--border)" bg="var(--card)">
<XStack borderBottomWidth={1} borderColor="var(--border)" bg="var(--muted)" px={16} py={8} items="center" gap={12}>
<Text fontFamily="$mono" fontSize="$1" color="var(--text-secondary)">
{t('Agent prompt')}
</Text>
<Box ml="auto">
<Take text={PROMPT} says="the agent prompt" label={t('Copy')} />
</Box>
</XStack>
<Line
render="pre"
display="block"
m={0}
p={20}
fontFamily="$mono"
fontSize="$2"
color="var(--text-secondary)"
whiteSpace="pre-wrap"
lineHeight="var(--leading-relaxed)"
$platform-web={{ overflowWrap: 'anywhere' }}
>
{PROMPT}
</Line>
</Box>
</Section>
<Section title={t('Results on Hanzo')} lede={t('What companies have done on the platform, each beside the page that shows it.')}>
<CardGrid
columns={3}
items={[
{
title: t('$8.4M in 30 days'),
meta: 'Damon',
description: t("Damon Motorcycles' CES 2020 launch sold $8.4M of motorcycles in 30 days at a 541:1 return on ad spend, on AI-generated ads."),
href: '/customers/damon-motorcycles',
},
{
title: t('60,384 people on $329'),
meta: 'Damon',
description: t('The pre-launch campaign reached 60,384 people on $329 of ad spend, and every AI-crafted ad set rated Above Average on Facebook within 36 hours.'),
href: '/customers/damon-motorcycles',
},
{
title: t('$1M in about 45 hours'),
meta: 'TechCrunch, 2014',
description: t('SKULLY, the first customer, ran the fastest fully funded campaign on Indiegogo.'),
href: 'https://techcrunch.com/2014/08/14/the-skully-smart-helmet-is-the-fastest-funded-indiegogo-campaign-to-hit-1m/',
},
]}
/>
</Section>
<Section title={t('You are a builder agent')} lede={t('With a person nearby: five steps from nothing to a working call, a tool server and your own measurement of Kai.')}>
<Step n={1} title={t('Get a credential')} sample={samples.key}>
{t('Install the CLI and sign in. On a machine with no browser it prints a link and an 8-character code, and your person approves it on any device. The token works as a bearer at once; mint a key for anything that runs longer than an hour.')}
</Step>
<Step n={2} title={t('Make the first call')} sample={samples.call}>
{t('zen-free costs nothing. The answer is the chat completions shape you already parse, and its headers say which model answered and how many free requests are left today.')}
</Step>
<Step n={3} title={t('Give your editor the tools')} sample={samples.mcp}>
{t('The same server loads in Codex, Cursor and Zed; their config files are on the docs page.')}
</Step>
<Step n={4} title={t('Measure Kai yourself')} sample={samples.kai}>
{t('Kai answers typed questions with probabilities over the options you name and writes no text, so usage.output_tokens is 0.')}
</Step>
<Step n={5} title={t('Run the frozen harness')} sample={samples.harness}>
{t('The harness that produced the published results is public. Three cases from four suites is 12 requests; the full harness is 9,499. The full runs are readable with no key at api.hanzo.ai/v1/research/runs.')}
</Step>
<Cta href={START} icon={KeyRound}>
{t('Get an API key, free')}
</Cta>
</Section>
<Section title={t('Run a campaign')} lede={t('Measure the site, have a model write the variants, and stage the campaign as a draft. A draft sends nothing and commits no budget; the launch spends it, so a person presses launch.')}>
<Box rounded="var(--radius-md)" borderWidth={1} borderColor="var(--border)" overflow="hidden">
{samples.campaign}
</Box>
<Text render="p" mt="$4" fontSize="$3" color="$soft" maxW={720}>
{t('Paid channels run through the ad accounts your organization connects (Meta, Google, X); email goes out from your organization with an unsubscribe link on every message. Metrics read the same analytics your event tag writes.')}
</Text>
</Section>
<Section title={t('You run builder agents')} lede={t('An account for your team, a budget on every agent, and a bill you can pay the way your agents can.')}>
<Prose>
<ul>
<li>{t('Sign up once. The account founds an organization you administer; Team is sold by the member, for 2 to 100 people, with single sign-on, roles and an audit log.')}</li>
<li>{t('Bound every agent. A key takes a monthly budget in cents, a rate in requests a minute, the models and projects it may reach, and an expiry. A session takes its own cap in micro-dollars, and pauses when it reaches it.')}</li>
<li>{t('Pay by card, Apple Pay or Google Pay at hanzo.ai/pay, or fund the balance in USDC or USDT over the API. Wire instructions come from GET /v1/billing/wire to a signed-in caller, and a wire is credited once the bank confirms it.')}</li>
<li>{t('Talk to the people who build it: book a call at cal.com/hanzo.')}</li>
</ul>
</Prose>
<XStack gap="$3" flexWrap="wrap" mt="$4">
<Cta href="/signup" icon={Building2}>
{t('Start free')}
</Cta>
<Cta href={CALL}>{t('Book a call')}</Cta>
</XStack>
</Section>
<Section title={t('Everything an agent can read')}>
<CardGrid
columns={2}
items={[
{ title: 'api.hanzo.ai/v1', description: t('Every capability the API serves, with no key; /v1/<capability> lists its operations.'), href: 'https://api.hanzo.ai/v1' },
{ title: 'api.hanzo.ai/v1/openapi.json', description: t('Every operation the API serves, with its schema.'), href: 'https://api.hanzo.ai/v1/openapi.json' },
{ title: 'hanzo.ai/llms.txt', description: t('This site for an agent: the offer, the prompt and every page as markdown.'), href: '/llms.txt' },
{ title: 'docs.hanzo.ai/llms.txt', description: t('Every docs page, one line each, with a markdown twin.'), href: 'https://docs.hanzo.ai/llms.txt' },
{ title: 'hanzoskills.com', description: t('Skills an agent installs to build with Hanzo.'), href: 'https://hanzoskills.com' },
{ title: t('Agents SDK'), description: t('A Python SDK for agents that hand work to each other behind a router.'), href: '/agents/sdk' },
]}
/>
</Section>
<Section>
<FaqJsonLd path="/agents" items={FAQ.map((f) => ({ name: f.q, answer: f.a }))} />
<Faq heading={t('Questions')} items={FAQ} />
<Text render="p" mt="$6" fontSize="$3" color="$soft">
{t("Hanzo AI: building business software since 2014. Techstars '17.")}{' '}
<a href="/about">{t('About Hanzo')}</a>
</Text>
</Section>
</Page>
)
}