Files
hanzo.ai/lib/agents.ts
zeekay ef25942f3a agents: sign up with a wallet, no human, and every page says where to start
A first-seen wallet signs a hanzo.id challenge, gets its own account and
organization on the Free plan, mints a key, makes a free call and asks for
a USDC deposit address: one TypeScript file, run as printed against
production, drawn at /agents#headless and named first in the prompt and
in llms.txt.

Every page's head now names /llms.txt, /agents, the API catalog and the
OpenAPI document; the Developers menu and its footer column lead with
"Agents: get an API key in one call"; the API catalog lists the sign-in,
key, deposit and MCP addresses.

Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
2026-10-26 13:39:25 -07:00

43 lines
8.4 KiB
TypeScript

/**
* What an agent reads first about Hanzo: the one prompt, said once.
*
* /agents draws it with a copy control, `scripts/llms.mjs` writes it into
* /llms.txt under "For agents", and docs.hanzo.ai/docs/agents/prompt and the
* hanzoskills.com `hanzo` skill carry the same bytes. Every command in it was run
* against the live API on 2026-10-26 (the record is LLM.md, "Agents"); a line
* that changes here changes in those three places too.
*/
export const PROMPT = [
"You can build on Hanzo, the AI operating system for business (building business software since 2014; Techstars '17). One API at https://api.hanzo.ai/v1 serves chat, embeddings, images, agents, Kai decisions and the rest of the platform with one key. Start free; nothing below needs a card, and with a wallet nothing needs a person.",
"",
"Get a credential:",
"- With a wallet, no human required: GET https://hanzo.id/v1/iam/web3/nonce?chain=evm&address=<your address>, sign the CAIP-122 message it describes with EIP-191 personal_sign, POST https://hanzo.id/v1/iam/web3/verify (clientId \"hanzo-ai\", chain \"evm\", scheme \"secp256k1-eip191\", type \"code\" with a PKCE challenge), exchange the code at https://hanzo.id/v1/iam/oauth/token, then POST https://api.hanzo.ai/v1/account/keys {\"name\":\"agent\",\"type\":\"secret\"}. A first-seen wallet gets its own account and organization on the Free plan. The whole flow as one TypeScript file: https://hanzo.ai/agents#headless",
"- With your person nearby: run `curl -fsSL https://hanzo.sh | sh`, then `hanzo auth login`. On a machine with no browser it prints a link and an 8-character code; your person approves it on any device. Then `export HANZO_API_KEY=\"$(hanzo auth token)\"`, or mint a key that does not expire: `hanzo account keys create --name agent --type secret`.",
"- On your own, with an inbox: POST https://hanzo.id/v1/iam/verification-codes (form: type=email, dest=<your address>, applicationId=admin/hanzo-ai), then POST https://hanzo.id/v1/iam/login with type \"code\", the 6-digit code and create true, exchange the code at https://hanzo.id/v1/iam/oauth/token, and POST https://api.hanzo.ai/v1/account/keys {\"name\":\"agent\",\"type\":\"secret\"}. The whole sequence: https://docs.hanzo.ai/docs/agents",
"",
"Free: 50 requests a day per person on `zen-free` and `enso-free`, and 50 Kai decisions a day. Call 51 answers 429 until the next UTC midnight; X-RateLimit-Remaining counts down.",
"",
"First call:",
"curl https://api.hanzo.ai/v1/chat/completions -H \"Authorization: Bearer $HANZO_API_KEY\" -H \"Content-Type: application/json\" -d '{\"model\":\"zen-free\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello\"}]}'",
"",
"Tools for your editor or agent: `npx -y --package=@hanzo/mcp@latest hanzo-mcp serve` (an MCP server over stdio with Kai, files, shell, git and search tools). Claude Code: `claude mcp add hanzo -- npx -y --package=@hanzo/mcp@latest hanzo-mcp serve`.",
"",
"Evaluate Kai, the decision model, yourself: POST https://api.hanzo.ai/v1/decisions with {\"model\":\"kai\",\"state\":...,\"questions\":...}; the frozen harness is public at https://github.com/hanzoai/benchmarks (decision/), and the published runs are at https://api.hanzo.ai/v1/research/runs?org=hanzo&benchmark=app.phishing",
"",
"Pay when you need more: plans from $20 a month at https://hanzo.ai/pricing (card, Apple Pay or Google Pay at https://hanzo.ai/pay), or top up over the API in USDC or USDT with no human: GET /v1/billing/crypto/options, then POST /v1/billing/crypto/deposit {\"chain\":\"base\",\"token\":\"usdc\",\"amountCents\":2000} and send to the address it returns; the balance is credited when the chain confirms. Per-key budgets and per-session caps keep spend bounded.",
"",
"Read more: https://docs.hanzo.ai/llms.txt (every docs page), https://hanzo.ai/llms.txt, https://api.hanzo.ai/v1/openapi.json (every operation), https://hanzoskills.com/llms.txt (skills). Talk to people at Hanzo: https://cal.com/hanzo, or https://hanzo.ai/contact.",
].join('\n')
/**
* The whole headless path in one file: a wallet signs in (CAIP-122, EIP-191), a
* first-seen wallet gets its own account and organization, a key is minted, the
* first call is free, and a USDC deposit address is asked for. Run verbatim
* against production on 2026-10-26 (`node headless.mts`, Node 22.18+), the same
* bytes docs.hanzo.ai/docs/agents and the hanzoskills `hanzo` skill print.
*/
export const HEADLESS = "// No human required: sign in with a wallet, get a key, call the API, top up in USDC.\n// A first-seen wallet gets its own Hanzo account and organization, on the Free plan.\n// npm i viem · node headless.mts (Node 22.18+; set HANZO_WALLET_KEY to use your own wallet)\nimport { createHash, randomBytes } from 'node:crypto'\nimport { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'\n\nconst ID = 'https://hanzo.id'\nconst API = 'https://api.hanzo.ai'\nconst CLIENT = 'hanzo-ai'\nconst REDIRECT = 'http://localhost:3000/callback'\n\n// Your agent's own wallet, or a fresh one. The key never leaves this process.\nconst account = privateKeyToAccount((process.env.HANZO_WALLET_KEY as `0x${string}`) ?? generatePrivateKey())\n\nconst json = async (r: Response) => {\n const body = await r.json()\n if (!r.ok || body?.status === 'error') throw new Error(`${r.url} ${r.status} ${JSON.stringify(body)}`)\n return body\n}\n\n// 1. A one-time challenge for this wallet.\nconst { data: c } = await json(await fetch(`${ID}/v1/iam/web3/nonce?chain=evm&address=${account.address}`))\n\n// 2. The CAIP-122 message the challenge describes, signed with EIP-191 personal_sign.\nconst message = [\n `${c.domain} wants you to sign in with your Ethereum account:`,\n account.address,\n '',\n ...(c.statement ? [c.statement, ''] : []),\n `URI: ${c.uri}`,\n `Version: ${c.version}`,\n `Nonce: ${c.nonce}`,\n `Issued At: ${c.issuedAt}`,\n ...(c.expirationTime ? [`Expiration Time: ${c.expirationTime}`] : []),\n].join('\\n')\nconst signature = await account.signMessage({ message })\n\n// 3. Verify: a first-seen wallet gets an account and an organization of its own.\nconst verifier = randomBytes(32).toString('base64url')\nconst challenge = createHash('sha256').update(verifier).digest('base64url')\nconst { data: code } = await json(\n await fetch(`${ID}/v1/iam/web3/verify`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n clientId: CLIENT, chain: 'evm', scheme: 'secp256k1-eip191', address: account.address, message, signature,\n type: 'code', redirectUri: REDIRECT, scope: 'openid profile email', codeChallenge: challenge, codeChallengeMethod: 'S256',\n }),\n }),\n)\n\n// 4. The authorization code for a token.\nconst token = await json(\n await fetch(`${ID}/v1/iam/oauth/token`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({ grant_type: 'authorization_code', client_id: CLIENT, code, code_verifier: verifier, redirect_uri: REDIRECT }),\n }),\n)\nconst bearer = { Authorization: `Bearer ${token.access_token}`, 'Content-Type': 'application/json' }\n\n// 5. A key that outlives the token. Shown once: keep it in your secret store.\nconst minted = await json(await fetch(`${API}/v1/account/keys`, { method: 'POST', headers: bearer, body: JSON.stringify({ name: 'agent', type: 'secret' }) }))\nconst key = { Authorization: `Bearer ${minted.key}`, 'Content-Type': 'application/json' }\n\n// 6. A first call, free.\nconst chat = await fetch(`${API}/v1/chat/completions`, {\n method: 'POST', headers: key,\n body: JSON.stringify({ model: 'zen-free', messages: [{ role: 'user', content: 'Reply with the single word: ready' }] }),\n})\nconsole.log('chat', chat.status, chat.headers.get('x-hanzo-served'), 'free left today:', chat.headers.get('x-ratelimit-remaining'))\nconsole.log((await chat.json()).choices?.[0]?.message?.content)\n\n// 7. Top up in USDC: ask for a deposit address, then send to exactly that address.\nconsole.log(await json(await fetch(`${API}/v1/billing/crypto/options`, { headers: key })))\nconsole.log(await json(await fetch(`${API}/v1/billing/crypto/deposit`, { method: 'POST', headers: key, body: JSON.stringify({ chain: 'base', token: 'usdc', amountCents: 500 }) })))\nconsole.log('HANZO_API_KEY=' + minted.key.slice(0, 12) + '… (store the whole key; it is shown once)')"
/** Where an agent goes next: the docs page with every request spelled out. */
export const START = 'https://docs.hanzo.ai/docs/agents'