Build AI chat interfaces for the terminal with @assistant-ui/react-ink.
Quick Start
The fastest way to get started with assistant-ui for the terminal.
Create a new project
npx assistant-ui@latest create --ink my-chat-app
cd my-chat-appInstall dependencies
pnpm installStart the app
pnpm devManual Setup
If you prefer to add assistant-ui to an existing Node.js project, follow these steps.
Install dependencies
npm install @assistant-ui/react-ink @assistant-ui/react-ink-markdown ink reactCreate a chat model adapter
Define how your app communicates with your AI backend. This example uses a simple streaming adapter:
import type { ChatModelAdapter } from "@assistant-ui/react-ink";
export const myChatAdapter: ChatModelAdapter = {
async *run({ messages, abortSignal }) {
const response = await fetch("http://localhost:3000/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ messages }),
signal: abortSignal,
});
const reader = response.body?.getReader();
if (!reader) throw new Error("No response body");
const decoder = new TextDecoder();
let fullText = "";
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
fullText += chunk;
yield { content: [{ type: "text", text: fullText }] };
}
},
};This is the same ChatModelAdapter interface used in @assistant-ui/react and @assistant-ui/react-native. If you already have an adapter, you can reuse it as-is.
Set up the runtime
import { Box, Text } from "ink";
import {
AssistantProvider,
useLocalRuntime,
} from "@assistant-ui/react-ink";
import { myChatAdapter } from "./adapters/my-chat-adapter.js";
export function App() {
const runtime = useLocalRuntime(myChatAdapter);
return (
<AssistantProvider runtime={runtime}>
<Box flexDirection="column" padding={1}>
<Text bold color="cyan">My Terminal Chat</Text>
{/* your chat UI */}
</Box>
</AssistantProvider>
);
}Build your chat UI
Use primitives to compose your terminal chat interface:
import { Box, Text } from "ink";
import {
ThreadRoot,
ThreadMessages,
ThreadEmpty,
ComposerInput,
useThreadIsRunning,
} from "@assistant-ui/react-ink";
import type { ThreadMessage } from "@assistant-ui/react-ink";
import { MarkdownText } from "@assistant-ui/react-ink-markdown";
const Message = ({ message }: { message: ThreadMessage }) => {
const isUser = message.role === "user";
const text = message.content
.filter((p) => p.type === "text")
.map((p) => ("text" in p ? p.text : ""))
.join("");
if (isUser) {
return (
<Box marginBottom={1}>
<Text bold color="green">You: </Text>
<Text wrap="wrap">{text}</Text>
</Box>
);
}
return (
<Box flexDirection="column" marginBottom={1}>
<Text bold color="blue">AI:</Text>
<MarkdownText text={text} />
</Box>
);
};
const StatusIndicator = () => {
const isRunning = useThreadIsRunning();
if (!isRunning) return null;
return (
<Box marginBottom={1}>
<Text color="yellow">Thinking...</Text>
</Box>
);
};
export const Thread = () => {
return (
<ThreadRoot>
<ThreadEmpty>
<Box marginBottom={1}>
<Text dimColor>No messages yet. Start typing below!</Text>
</Box>
</ThreadEmpty>
<ThreadMessages renderMessage={(props) => <Message {...props} />} />
<StatusIndicator />
<Box borderStyle="round" borderColor="gray" paddingX={1}>
<Text color="gray">{"> "}</Text>
<ComposerInput
submitOnEnter
placeholder="Type a message..."
autoFocus
/>
</Box>
</ThreadRoot>
);
};Render with Ink
import { render } from "ink";
import { App } from "./app.js";
render(<App />);What's Next?
Migration from WebAlready using assistant-ui? Migrate your web app to the terminal.Custom BackendConnect to your own backend API or manage threads server-side.PrimitivesComposable terminal UI components for building chat interfaces.Example AppFull terminal chat example with markdown rendering and streaming.