Files
hanzo.ai/components/dev/DevLanding.tsx
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

378 lines
16 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.
/**
* /dev, signed out — Hanzo Dev, the coding agent, told in the order a reader
* asks: what it is and where it runs, how to get it, what it thinks with, why
* its work can be checked, how it is fenced, how it relates to Gen, and where
* its source is. A signed-in reader never sees this page: the same address
* draws the builder (app/dev/_door.tsx).
*
* Every command here was run against the binary it names — `dev --help`,
* `dev login --help`, `dev cloud --help` and `hanzo dev --help`. No figure is
* typed: the one number the page shows, the latest release, is read from
* GitHub when the page opens (components/product/Release) and is absent when it does not
* answer.
*
* A server component, so the samples are highlighted where the page is built
* (components/code); the pieces that move are the client parts it stacks.
*/
import type { ReactNode } from 'react'
import { SiteFooter, SiteHeader } from '@/components/home/shell'
import { Hero } from '@/components/family/Hero'
import Race from '@/components/home/Race'
import { Read } from '@/components/family/Read'
import Closing from '@/components/marketing/Closing'
import { Band, Card, Head } from '@/components/marketing/band'
import { Rise } from '@/components/marketing/rise'
import { Sample } from '@/components/models/kit'
import { Term, type Line } from '@/components/frame/Term'
import { Code } from '@/components/code'
import { Box, Text, XStack, YStack } from '@/components/ui'
import { Faq } from '@/components/ui/faq'
import { Grid } from '@hanzo/ui/grid'
import { More } from '@hanzo/ui/marketing'
import { LOGIN } from '@hanzo/app/lib/destination'
import { dev } from '@hanzo/app/lib/host'
import { Release } from '@/components/product/Release'
import { AgentPrompt } from '@/components/product/AgentPrompt'
import { product } from '@/lib/product'
const DOCS = 'https://docs.hanzo.ai/docs/dev'
const REPO = 'https://github.com/hanzoai/dev'
/** Open Dev: sign in, then back to /dev, where a signed-in reader is drawn the builder. */
const OPEN = `${LOGIN}?next=${encodeURIComponent(dev())}`
/** The hero's transcript: the commands themselves, nothing they print. */
const START: Line[] = [
{ kind: 'run', text: 'curl -fsSL https://hanzo.sh | sh' },
{ kind: 'gap' },
{ kind: 'run', text: 'dev' },
{ kind: 'note', text: 'a session in this repository' },
{ kind: 'gap' },
{ kind: 'run', text: 'dev exec "fix the failing test"' },
{ kind: 'note', text: 'the same task, run to the end without the interface' },
{ kind: 'gap' },
{ kind: 'run', text: 'dev cloud exec "add rate limiting"' },
{ kind: 'note', text: 'handed to Hanzo’s cloud; dev cloud apply brings the diff back' },
]
/** Words set as code inside a sentence. */
function Mono({ children }: { children: ReactNode }) {
return <Text fontFamily="$mono">{children}</Text>
}
/** Where Dev runs. Each is the same agent on the same account. */
const PLACES: readonly { name: string; body: ReactNode; command?: string }[] = [
{
name: 'Terminal',
command: 'dev',
body: 'In any repository. It reads the code, makes the change and runs commands in a sandbox. Bare hanzo opens the same session.',
},
{
name: 'Web',
command: 'hanzo.ai/dev',
body: 'Describe an app. Dev writes it, runs it and deploys it, with the database, the sign-in and the storage already there.',
},
{
name: 'Cloud',
command: 'dev cloud exec',
body: 'Hand a task to Hanzo’s cloud and close the laptop. dev cloud diff shows what it did; dev cloud apply puts it in your checkout.',
},
{
name: 'Slack',
body: 'An org admin connects the workspace in Dev’s settings, and the team talks to it from their channels.',
},
]
/** The three ways in, each the line a reader types. */
const INSTALL: readonly { name: string; code: string; note: string }[] = [
{ name: 'hanzo.sh', code: 'curl -fsSL https://hanzo.sh | sh', note: 'Linux and macOS. One native binary, its checksum verified.' },
{ name: 'npm', code: 'npm install -g @hanzo/dev', note: 'Every platform, Windows included.' },
{ name: 'Homebrew', code: 'brew install hanzoai/tap/hanzo-dev', note: 'macOS and Linux.' },
]
const SIGNIN = `dev login # pick an account: Hanzo is the default
dev login --hanzo # straight to your Hanzo account
dev login --with-api-key # or paste a Hanzo API key, hidden`
const MODEL = `dev # enso-auto, through api.hanzo.ai
dev --model enso-ultra # name any model Hanzo serves
/model zen5-coder # or switch inside a session`
const FENCE = `hanzo dev --ask # ask before each action
hanzo dev --no-sandbox # run unconfined, for this run only
hanzo dev --project-mcp # also load this repository's .mcp.json`
/** One step of Dev's loop, cheapest first: the first that holds is taken. */
const STEPS: readonly [string, string][] = [
['An action applies', 'One candidate, its preconditions hold: it runs, and no model is asked.'],
['Kai decides', 'Where to fix, what to do and how hard to try, asked of Kai in one structured call.'],
['Generate, low effort', 'A model writes the edit, asked for as a strict schema.'],
['Generate, full effort', 'The same, given room to reason.'],
['Recover', 'A failure seen before with nothing changed picks a different action, or escalates. It never replays the loop.'],
]
/** Why a run can be checked, each in the controller's own terms. */
const CHECKED: readonly [string, string][] = [
['Verified, not predicted', 'A step is settled by what the verifier records — the exit status, the tests, the diff — never by what anything expected.'],
['Bound to the revision', 'Preconditions are checked just before an action runs, and an edit voids every decision made against the files as they were.'],
['A ledger you can replay', 'Every decision, fallback, run and verification is a line with a stable id and its reason, credentials redacted, so its time and cost can be read back.'],
]
const QUESTIONS = [
{
question: 'What does it cost?',
answer: 'Dev is free and open source. Its model calls go through api.hanzo.ai and are metered on your Hanzo account at the price list’s rates, so one account and one bill cover every model.',
},
{
question: 'Which systems does it run on?',
answer: 'Linux, macOS and Windows. hanzo.sh installs it on Linux and macOS, npm everywhere, Homebrew on macOS and Linux.',
},
{
question: 'How does it update?',
answer: 'The way it was installed: from its own release, from npm, or from Homebrew.',
},
{
question: 'Can I bring a key instead of signing in?',
answer: 'Yes. dev login --with-api-key takes a Hanzo API key, hidden as it is typed or read from piped stdin.',
},
{
question: 'How is Dev different from Gen?',
answer: 'Dev is Hanzo’s coding agent. Gen is the harness that runs any coding agent, Dev included, on Hanzo’s tools and models.',
},
]
/** A label and its body, as the cards on this page set them. */
function Tile({ name, body, command, beat }: { name: string; body: ReactNode; command?: string; beat: number }) {
return (
<Rise beat={beat}>
<Card display="flex" flexDirection="column" gap="$2" p="$5" height="100%">
<Text render="h3" fontSize="$5" lineHeight="$5" fontWeight="600" color="$ink">
{name}
</Text>
{command ? (
<Text fontFamily="$mono" fontSize="$2" lineHeight="$2" color="var(--text-secondary)">
{command}
</Text>
) : null}
<Text fontSize="$3" lineHeight="$3" color="var(--text-secondary)">
{body}
</Text>
</Card>
</Rise>
)
}
/** A name, then what it means, one per row under a hairline — the shape of /kai's status list. */
function Rows({ rows }: { rows: readonly [string, string][] }) {
return (
<YStack>
{rows.map(([name, body]) => (
<XStack key={name} gap="$4" py="$4" borderTopWidth={1} borderColor="var(--border)" items="baseline" flexWrap="wrap">
<Text width={220} $max-sm={{ width: '100%' }} fontSize="$3" lineHeight="$3" fontWeight="600" color="$ink">
{name}
</Text>
<Text flex={1} minW={220} fontSize="$3" lineHeight="$3" color="var(--text-secondary)">
{body}
</Text>
</XStack>
))}
</YStack>
)
}
export default function DevLanding() {
return (
<>
<SiteHeader />
<Box render="main" bg="$background" pt="$10" $platform-web={{ color: 'var(--foreground)' }}>
<Hero
chip="Hanzo Dev · Coding agent"
title={
<>
Describe the change.
<br />
Dev ships it.
</>
}
lede="Hanzo Dev is a coding agent. It reads your repository, makes the change, runs the tests and shows you the diff — in your terminal, on the web, in the cloud and in Slack."
primary={{ label: 'Get API key', href: 'https://platform.hanzo.ai/keys' }}
action={{ label: 'Explore GitHub examples →', href: REPO }}
more={{ label: 'Read the docs →', href: DOCS }}
figure={<Term title="terminal" lines={START} />}
/>
{/* The race: Dev beside Claude Code on the same held-out repairs, from the runs. */}
<Race />
<Band id="places" measure={1024}>
<Head eyebrow="One agent" title="Wherever the work is." lede="The same agent, the same models and the same account in every place it runs." loud />
<Grid columns={{ min: 220, max: 4 }} gap={12}>
{PLACES.map((p, n) => (
<Tile key={p.name} name={p.name} command={p.command} body={p.body} beat={n} />
))}
</Grid>
<XStack mt="$6" justify="center">
<More href={OPEN}>Open Dev on the web →</More>
</XStack>
</Band>
<Band id="install" measure={1024}>
<Head eyebrow="Install" title="One line, three ways." lede="Linux, macOS and Windows. Dev updates itself the way it was installed." loud />
<Grid columns={{ min: 280, max: 3 }} gap={16}>
{INSTALL.map((i, n) => (
<Rise key={i.name} beat={n} gap="$3">
<Sample lang={i.name}>
<Code lang="bash" code={i.code} />
</Sample>
<Text fontSize="$2" lineHeight="$2" color="var(--text-secondary)">
{i.note}
</Text>
</Rise>
))}
</Grid>
<YStack mt="$8" gap="$3" width="100%" maxW={720} mx="auto">
<Text fontSize="$3" lineHeight="$3" color="var(--text-secondary)" $platform-web={{ textAlign: 'center' }}>
Then sign in. Hanzo IAM links the machine to your account.
</Text>
<Sample lang="bash">
<Code lang="bash" code={SIGNIN} />
</Sample>
</YStack>
<XStack mt="$6" flexWrap="wrap" columnGap="$5" rowGap="$2" justify="center">
<Release repo="hanzoai/dev" />
<More href={DOCS}>Getting started →</More>
</XStack>
</Band>
<Band id="models" measure={896}>
<Head
eyebrow="Models"
title="Enso by default. Any model by name."
lede="Dev starts on enso-auto through api.hanzo.ai, so every call is metered on your Hanzo account. Every model Hanzo serves is a flag or a /model away."
loud
/>
<Sample lang="bash">
<Code lang="bash" code={MODEL} />
</Sample>
<XStack mt="$6" flexWrap="wrap" columnGap="$5" rowGap="$2" justify="center">
<More href="/enso">Enso →</More>
<More href="/models">Every model →</More>
</XStack>
</Band>
<Band id="kai" measure={896}>
<Head
eyebrow="Controller"
title="Kai decides. Dev does."
lede="Kai is Dev’s own controller, not an adviser. At each step it returns an operation Dev can run, or says plainly that it abstains. The cheapest step that holds is the one taken."
loud
/>
<Rows rows={STEPS} />
<XStack mt="$6" justify="center">
<More href="/kai">Kai, the decision model →</More>
</XStack>
</Band>
<Band id="checked" measure={1024}>
<Head eyebrow="Proof" title="Built to be checked." lede="Nothing counts as done because a model said so." loud />
<Grid columns={{ min: 260, max: 3 }} gap={12}>
{CHECKED.map(([name, body], n) => (
<Tile key={name} name={name} body={body} beat={n} />
))}
</Grid>
</Band>
<Band id="sandbox" measure={896}>
<Head
eyebrow="Sandbox"
title="Inside the fence you set."
lede="Commands run in a sandbox and are approved inside it. Ask before every action, or lift the fence for one run. A repository’s own tool servers stay off until you say you trust it."
loud
/>
<Sample lang="bash">
<Code lang="bash" code={FENCE} />
</Sample>
</Band>
<Band id="gen" measure={896}>
<Head
eyebrow="Gen"
title="Dev is our agent. Gen runs any agent."
lede={
<>
Hanzo Gen is the harness: Claude Code, Codex, Cursor and the rest, on Hanzo’s tools and models and linked to your
account. Dev is the agent it opens when none is named.
</>
}
loud
/>
<XStack justify="center">
<More href="/gen">Hanzo Gen →</More>
</XStack>
</Band>
<Band id="source" measure={896}>
<Head
eyebrow="Open source"
title="Read every line."
lede={
<>
Dev is Apache-2.0 on GitHub. Build it, change it, send a pull request — <Mono>hanzoai/dev</Mono>.
</>
}
loud
/>
<XStack flexWrap="wrap" columnGap="$5" rowGap="$2" justify="center">
<More href={REPO}>The source →</More>
<More href={`${REPO}/releases`}>Releases →</More>
</XStack>
</Band>
<Band measure={768}>
<Head eyebrow="Questions" title="Asked plainly." />
<Faq items={QUESTIONS} />
</Band>
<Read
eyebrow="Read"
title="The agent, its controller, its source."
lede="Where to start, what Dev thinks with, and the code."
groups={[
{
label: 'Start',
links: [
{ href: DOCS, label: 'Docs →', note: 'Install, sign in, the first session.' },
{ href: '#install', label: 'Install →', note: 'hanzo.sh, npm or Homebrew.' },
{ href: '/cli', label: 'Hanzo CLI →', note: 'hanzo, and the session it opens.' },
],
},
{
label: 'Models',
links: [
{ href: '/enso', label: 'Enso →', note: 'The default.' },
{ href: '/kai', label: 'Kai →', note: 'The controller.' },
{ href: '/models', label: 'Every model →' },
],
},
{
label: 'Source',
links: [
{ href: REPO, label: 'hanzoai/dev →', note: 'Apache-2.0.' },
{ href: `${REPO}/releases`, label: 'Releases →' },
{ href: '/gen', label: 'Hanzo Gen →', note: 'The harness for every agent.' },
{ href: '/kata', label: 'Hanzo Kata →', note: 'Test, repair, and the pull request.' },
],
},
]}
/>
{product('dev') && <AgentPrompt product={product('dev')!} />}
<Closing title="Describe the change. Dev opens the pull request." secondary={{ label: 'Dev on GitHub', href: 'https://github.com/hanzoai/dev' }} />
</Box>
<SiteFooter />
</>
)
}