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-sdk

Optional 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
messagesAccumulated SDKMessage[] (assistant / user / tool).
streamingTextLive text of the in-flight assistant turn (from stream_event deltas).
status'idle' | 'running' | 'paused' (paused = survivor mid-continuation).
tokens / costFrom 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).

ComponentKey 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 dev

Set 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.