FT

fancy-term

@particle-academy/fancy-term

Human+ Terminal — a controlled, themeable <Terminal> over xterm.js with hooks and an MCP-bridgeable surface agents read + drive.

TSUI · 1 component1 components
$npm install @particle-academy/fancy-term
Authoring surface
Humans compose it

Controlled value + onChange, JSON-friendly props, terse defaults — so people (and agents) build it fast.

Inhabited surface
Agents drive it over MCP

A bridge in @particle-academy/agent-integrations exposes typed tools — agents read and mutate state through stable handles, never DOM scraping.

terminal_readterminal_writeterminal_run
Why
A terminal is where humans and agents do real work — run a command, watch the output, decide what's next. Most apps embed xterm.js raw: an uncontrolled DOM widget that an embedded agent can only read by scraping pixels and can only drive by faking keystrokes. That breaks the moment the layout shifts. fancy-term makes the terminal a proper Human+ surface — controlled so the host owns the buffer, themeable so it's sexy by default, and bridgeable so an agent reads the visible buffer and writes input through a stable handle, never the DOM. And because running a command is irreversible, it carries the trust-but-verify affordance: an agent proposes a command and a human confirms before it runs.
What
A React <Terminal> wrapping xterm.js. Output is a controlled buffer — pass output and the component writes only the appended delta as it grows (so you can stream command output straight from React state, e.g. via fancy-query's useFancyStream); onData forwards the user's keystrokes upstream. It carries a stable data-fancy-terminal handle plus a ref exposing a TerminalHandlewrite / writeln / clear / reset / fit / focus / getBuffer() (the visible text an agent "sees") / getSelection() / .xterm (escape hatch). Three hooks ship the headless engine layer: useTerminal (create/own the xterm instance), useTerminalFit (ResizeObserver auto-fit, guarding the hidden-tab / late-mount 0×0 trap), and useTerminalSession (bind to a streamed PTY / command backend). xterm + @xterm/addon-fit are peer dependencies — the wrapper stays zero-runtime-dep, the same posture as fancy-echarts over ECharts — and a Fancy dark theme (drawn from the react-fancy Tailwind v4 tokens) is the default.
How
Install npm i @particle-academy/fancy-term @xterm/xterm @xterm/addon-fit and import the xterm stylesheet once: import "@xterm/xterm/css/xterm.css";. The parent needs a height (the terminal fits its container): <div style={{ height: 360 }}><Terminal output={out} onData={(d) => backend.send(d)} /></div>. Drive it from React state via output, or imperatively through the ref. Bind a backend with useTerminalSession({ transport: { send, subscribe } })<Terminal output={session.output} onData={session.sendData} />. To make it inhabitable, register the bridge from @particle-academy/agent-integrations: registerTerminalBridge(server, { adapter }) exposes terminal_read / terminal_write / terminal_run; turn on pendingMode and destructive commands are staged for a human to confirm (terminal_confirm / terminal_reject). See the xterm.js API docs for the underlying terminal options and addon ecosystem.

Components

1 component · click any tile for a full demo, source, and install snippet.

TerminalOpen →
⚡ PowerShell ▾
Fancy Term — a Human+ terminal.
PS C:\fancy> npm run build
✓ built in 1.2s
PS C:\fancy>
Controlled xterm.js terminal — agent-bridgeable.