~/project$ pnpm test✓ src/utils.test.ts (5 tests) 23ms
✓ src/api.test.ts (12 tests) 156ms
✓ src/components.test.ts (8 tests) 89ms
Test Files 3 passed (3)
Tests 25 passed (25)
Start at 10:23:45
Duration 312ms<Terminal command="pnpm test" stdout={`[32m✓[0m src/utils.test.ts [90m(5 tests)[0m [33m23ms[0m[32m✓[0m src/api.test.ts [90m(12 tests)[0m [33m156ms[0m[32m✓[0m src/components.test.ts [90m(8 tests)[0m [33m89ms[0m[1mTest Files[0m [32m3 passed[0m (3)[1m Tests[0m [32m25 passed[0m (25)[1m Start at[0m 10:23:45[1m Duration[0m 312ms`} exitCode={0} durationMs={312} cwd="~/project"/>Renders colored terminal output exactly as it appears in your shell
Clear visual indicators for success and failure states
Shows how long the command took to execute
Errors are visually distinct from standard output
Copy components/tool-ui/terminal and the shared directory into your project. The shared folder contains utilities used by all Tool UI components. The tool-ui directory should sit alongside your shadcn ui directory.
This component requires the following shadcn/ui components:
pnpm dlx shadcn@latest add buttonIt also uses Radix Collapsible directly:
pnpm add @radix-ui/react-collapsibleInstall dependencies (ansi-to-react for ANSI color parsing):
pnpm add ansi-to-reactimport { Terminal } from "@/components/tool-ui/terminal";export function MyComponent() { return ( <Terminal id="my-terminal-example" command="ls -la" stdout={`total 16drwxr-xr-x 3 user user 4096 Dec 3 10:23 .drwxr-xr-x 12 user user 4096 Dec 3 10:20 ..-rw-r--r-- 1 user user 220 Dec 3 10:23 package.json`} exitCode={0} durationMs={45} cwd="~/project" /> );}Define a tool that returns a terminal result payload, then render the component with runtime validation.
// Backend toolimport { tool, jsonSchema } from "ai";const showTerminal = tool({ description: "Show a command result", inputSchema: jsonSchema<{}>({ type: "object", properties: {}, additionalProperties: false, }), async execute() { return { id: "terminal-1", command: "pnpm test", cwd: "~/project", exitCode: 0, durationMs: 1288, stdout: "\u001b[32m✓\u001b[0m 42 tests passed", }; },});// Frontend with assistant-uiimport { makeAssistantToolUI } from "@assistant-ui/react";import { Terminal, TerminalErrorBoundary, parseSerializableTerminal,} from "@/components/tool-ui/terminal";function ParsedTerminal({ result }: { result: unknown }) { const props = parseSerializableTerminal(result); return <Terminal {...props} />;}export const ShowTerminalUI = makeAssistantToolUI({ toolName: "showTerminal", render: ({ result }) => { // Tool outputs stream in; `result` will be `undefined` until the tool resolves. if (result === undefined) { return <Terminal id="loading" command="…" exitCode={0} isLoading />; } return ( <TerminalErrorBoundary> <ParsedTerminal result={result} /> </TerminalErrorBoundary> ); },});Prop
Type
Prop
Type
Prop
Type
Prop
Type
Terminal automatically renders ANSI escape codes for colored output:
import { Terminal } from "@/components/tool-ui/terminal";const coloredOutput = "\x1b[32m✔\x1b[0m Success\n\x1b[31m✗\x1b[0m Error";<Terminal id="my-terminal" command="npm test" stdout={coloredOutput} exitCode={0}/>;Common ANSI codes:
\x1b[32m - Green text\x1b[31m - Red text\x1b[33m - Yellow text\x1b[36m - Cyan text\x1b[1m - Bold text\x1b[0m - Reset formatting0 - Success (green)1 - General error (red)2 - Misuse of shell builtins (red)126 - Command not executable (red)127 - Command not found (red)130 - Interrupted with Ctrl+C (red)<Terminal
command="pnpm test"
stdout={`[32m✓[0m src/utils.test.ts [90m(5 tests)[0m [33m23ms[0m
[32m✓[0m src/api.test.ts [90m(12 tests)[0m [33m156ms[0m
[32m✓[0m src/components.test.ts [90m(8 tests)[0m [33m89ms[0m
[1mTest Files[0m [32m3 passed[0m (3)
[1m Tests[0m [32m25 passed[0m (25)
[1m Start at[0m 10:23:45
[1m Duration[0m 312ms`}
exitCode={0}
durationMs={312}
cwd="~/project"
/>pnpm dlx shadcn@latest add buttonpnpm add @radix-ui/react-collapsiblepnpm add ansi-to-reactimport { Terminal } from "@/components/tool-ui/terminal";
export function MyComponent() {
return (
<Terminal
id="my-terminal-example"
command="ls -la"
stdout={`total 16
drwxr-xr-x 3 user user 4096 Dec 3 10:23 .
drwxr-xr-x 12 user user 4096 Dec 3 10:20 ..
-rw-r--r-- 1 user user 220 Dec 3 10:23 package.json`}
exitCode={0}
durationMs={45}
cwd="~/project"
/>
);
}// Backend tool
import { tool, jsonSchema } from "ai";
const showTerminal = tool({
description: "Show a command result",
inputSchema: jsonSchema<{}>({
type: "object",
properties: {},
additionalProperties: false,
}),
async execute() {
return {
id: "terminal-1",
command: "pnpm test",
cwd: "~/project",
exitCode: 0,
durationMs: 1288,
stdout: "\u001b[32m✓\u001b[0m 42 tests passed",
};
},
});
// Frontend with assistant-ui
import { makeAssistantToolUI } from "@assistant-ui/react";
import {
Terminal,
TerminalErrorBoundary,
parseSerializableTerminal,
} from "@/components/tool-ui/terminal";
function ParsedTerminal({ result }: { result: unknown }) {
const props = parseSerializableTerminal(result);
return <Terminal {...props} />;
}
export const ShowTerminalUI = makeAssistantToolUI({
toolName: "showTerminal",
render: ({ result }) => {
// Tool outputs stream in; `result` will be `undefined` until the tool resolves.
if (result === undefined) {
return <Terminal id="loading" command="…" exitCode={0} isLoading />;
}
return (
<TerminalErrorBoundary>
<ParsedTerminal result={result} />
</TerminalErrorBoundary>
);
},
});import { Terminal } from "@/components/tool-ui/terminal";
const coloredOutput = "\x1b[32m✔\x1b[0m Success\n\x1b[31m✗\x1b[0m Error";
<Terminal
id="my-terminal"
command="npm test"
stdout={coloredOutput}
exitCode={0}
/>;