React UI kit — anyclaude-react
Restylable React hooks + components for building chatbots, agents, research assistants, and full browser IDEs on top of anyclaude-sdk — with built-in serverless survivor stream-stitching.
npm install anyclaude-react # peer: react >=18, anyclaude-sdkOptional default theme: import 'anyclaude-react/styles.css'. Everything is restylable — components take a className and read CSS variables (--ac-bg, --ac-accent, …), or skip the stylesheet and target the .ac-* classes / Tailwind.
useAgent()
The core hook. Drive it with one of { run } (in-process, wraps query()), { endpoint } (a serverless NDJSON endpoint), or a prebuilt { client }.
import { useAgent } from 'anyclaude-react'
import { query } from 'anyclaude-sdk/query'
function Chat({ workspace, llm }) {
const { messages, streamingText, status, tokens, cost, send, interrupt, clear } = useAgent({
run: ({ prompt, sessionId, continueRun }) =>
query({ prompt, workspace, llm, model: 'gpt-4o', sessionId, resume: !!continueRun, continueRun, includePartialMessages: true }),
})
return /* render messages + an input that calls send(text) */
}| Returns | |
|---|---|
messages | Accumulated SDKMessage[] (assistant / user / tool). |
streamingText | Live text of the in-flight assistant turn (from stream_event deltas). |
status | 'idle' | 'running' | 'paused' (paused = survivor mid-continuation). |
tokens / cost | From the result message. |
send(text) · interrupt() · clear() | Submit a turn · abort · reset. |
Clients & survivor stitching
The clients yield one unified AsyncIterable<SDKMessage> per logical run. When a run emits {type:'system',subtype:'paused'} (the SDK hit maxDurationMs), the client transparently fires a continuation (continueRun, same sessionId) and keeps yielding — so a serverless function's time cap is invisible end-to-end.
import { createAgentClient } from 'anyclaude-react'
const client = createAgentClient({ run: ({ prompt, sessionId, continueRun }) => query({ /* … */ }) })
useAgent({ client })import { createEndpointClient } from 'anyclaude-react'
const client = createEndpointClient({ endpoint: '/api/agent' }) // POST → NDJSON stream
useAgent({ client }) // or simply: useAgent({ endpoint: '/api/agent' })Chat components
<AgentChat>
All-in-one: Transcript + Working + Composer wired to useAgent (takes its options).
<ChatPanel>
Like AgentChat with a header bar showing title + live status / tokens / cost.
<Transcript>
Renders the message list (text, tool pills).
<MarkdownMessage>
Assistant bubble rendered with streamdown (streaming-aware markdown + code). Override via render.
<Composer>
Textarea + send (Enter sends, Shift+Enter newline).
<Working>
Shimmering "Working…" indicator while running.
<ToolCall>
Collapsible tool call + result pill.
import { ChatPanel } from 'anyclaude-react'
<ChatPanel run={run} title="Assistant" placeholder="Ask anything…" />IDE components
For browser IDEs / agent workbenches. These need optional peers: @xterm/xterm + @xterm/addon-fit (Terminal) and codemirror + @codemirror/* (CodeEditor).
| Component | Key props |
|---|---|
<Terminal> | spawn(size) => Promise<ShellProcess> (a streaming shell — e.g. wc.spawn('jsh',{terminal:size})), title?, fontSize?, className? |
<FileExplorer> | list(dir) => Promise<{name,isDir}[]>, root?, openPath?, onOpen, refreshKey?, className? |
<CodeEditor> | value, onChange?, language?, readOnly?, className? (CodeMirror 6, controlled) |
import { WebContainer } from '@webcontainer/api'
import { Terminal, FileExplorer, CodeEditor, ChatPanel } from 'anyclaude-react'
const wc = await WebContainer.boot()
<Terminal spawn={(size) => wc.spawn('jsh', { terminal: size })} />
<FileExplorer list={(d) => wc.fs.readdir(d, { withFileTypes: true }).then(es => es.map(e => ({ name: e.name, isDir: e.isDirectory() })))} root="/home/projects" onOpen={open} />
<CodeEditor value={code} onChange={setCode} />See the browser-ide example for the full wiring (real jsh shell + Node, no backend).
useWebContainerPreview() — live preview 0.5.0
The fiddly part of an in-browser IDE: boot a dev server inside a WebContainer and get back a live preview URL. The hook spawns the command, waits for the container's server-ready event, streams logs, and cleans up on unmount.
import { useWebContainerPreview } from 'anyclaude-react'
const { url, status, logs, restart } = useWebContainerPreview({ wc, autoStart: true })
// command='npm', args=['run','dev'] by default; pass spawnOptions={{ cwd, env }}
return url ? <iframe src={url} /> : <pre>{logs || status}</pre>Scaffold a bolt clone in one command new
create-anyclaude-app spins up a ready-to-run in-browser AI IDE — WebContainer + chat + live preview, wired with useWebContainerPreview and a browser-side query(). No backend.
npm create anyclaude-app@latest my-app # template: bolt
cd my-app && npm install && npm run devSet a CORS-enabled LLM endpoint + model + key in the UI, then ask it to build something — it edits files in a real shell in the tab, and the preview updates. (For non-CORS / key-protected models, move the loop server-side — see Deploy.)
<AskUser> — interactive questions
Renders the SDK's ask_user_question tool as option buttons. Pass onAskUser to query, stash the question + resolver in state, render <AskUser>, and resolve from onAnswer.
import { AskUser, type AskUserQuestion } from 'anyclaude-react'
const [q, setQ] = useState<{ q: AskUserQuestion; resolve: (a: string | string[]) => void } | null>(null)
const onAskUser = (question) => new Promise((resolve) => setQ({ q: question, resolve }))
// pass onAskUser to query({ … }), then:
{q && <AskUser question={q.q} onAnswer={(a) => { q.resolve(a); setQ(null) }} />}Examples
browser-ide
WebContainer IDE — real shell + Node in the tab, all four IDE components.
vercel-clienttools
Serverless brain + browser-executed bash (client-side tools).
vercel-kv-survivor
Serverless survivor with Vercel KV.
vercel-supabase-survivor
Serverless survivor with Supabase.
vercel-indexeddb-survivor
Stateless function + browser IndexedDB session.
browser-chat
Minimal browser chatbot.