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.

UI surfaceTS 1 components
Part of Fancy Term2 packages, one productSee the family
UI surface
1 components, each with a live demo, props table, and source.
$ fancy --help
ButtonlivebetaCardRK
$npm install @particle-academy/fancy-term
One handler, two operatorsHuman+ contract
Human — keystroke
<Terminal
  value={value}
  onChange={setValue}
/>
Agent — MCP tool call
tools.call(
  "terminal_read",
  { … }
)
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.
What next
Click any component for a live demo, props, and source — or install the package and go.