# Migration from Web URL: /docs/ink/migration Migrate an existing @assistant-ui/react app to the terminal with React Ink. If you already have an assistant-ui web app, most of your code transfers directly. The runtime core is shared between `@assistant-ui/react` and `@assistant-ui/react-ink` via `@assistant-ui/core` — only the UI layer changes. What stays the same \[#what-stays-the-same] * **Runtime setup** — `useLocalRuntime`, `ChatModelAdapter`, and all runtime options work identically. * **AI SDK integration** — `@assistant-ui/react-ai-sdk` works with React Ink. Your runtime setup transfers directly. * **Tool definitions** — `useAssistantTool`, `makeAssistantTool`, and tool UI renderers use the same API. * **State hooks** — `useAuiState`, `useAui`, and selector patterns are the same. * **Backend code** — Your API routes, streaming endpoints, and server-side logic need zero changes. What changes \[#what-changes] | Web (`@assistant-ui/react`) | Terminal (`@assistant-ui/react-ink`) | | ----------------------------------------- | ----------------------------------------------------------- | | `AssistantRuntimeProvider` | `AssistantProvider` (same purpose) | | DOM primitives (`div`, `button`, `input`) | Ink primitives (`Box`, `Text`, `TextInput`) | | CSS / Tailwind styling | Ink's flexbox + ANSI colors | | `@assistant-ui/ui` (shadcn components) | Build your own terminal UI with primitives | | `@assistant-ui/react-markdown` | `@assistant-ui/react-ink-markdown` (ANSI-rendered markdown) | Step-by-step \[#step-by-step] Install the React Ink package \[#install-the-react-ink-package] ```sh npm install @assistant-ui/react-ink ink react ``` Keep your runtime hook \[#keep-your-runtime-hook] Your `useLocalRuntime` setup works as-is — no changes needed: ```tsx title="hooks/use-app-runtime.ts" // This file is identical to your web version import { useLocalRuntime, type ChatModelAdapter } from "@assistant-ui/react-ink"; export function useAppRuntime(adapter: ChatModelAdapter) { return useLocalRuntime(adapter); } ``` If you use a monorepo, you can share the adapter between web and terminal projects directly. Replace the provider \[#replace-the-provider] ```tsx // Before (web) // import { AssistantRuntimeProvider } from "@assistant-ui/react"; // After (Terminal) — same concept, different package import { AssistantProvider } from "@assistant-ui/react-ink"; export function App() { const runtime = useAppRuntime(adapter); return ( {/* your terminal UI */} ); } ``` Rebuild the UI layer \[#rebuild-the-ui-layer] This is the main migration effort. Web components don't render in the terminal. Replace them with Ink primitives: ```tsx // Web — DOM-based // import { Thread } from "@/components/assistant-ui/thread"; // Terminal — use Ink primitives import { ThreadRoot, ThreadMessages, ComposerInput, } from "@assistant-ui/react-ink"; import { Box, Text } from "ink"; function ChatScreen() { return ( ( {message.content.filter((p) => p.type === "text").map((p) => p.text).join("\n")} )} /> ); } ``` See the [Primitives reference](/docs/ink/primitives) for the full list of available components. Monorepo code sharing \[#monorepo-code-sharing] In a monorepo, you can share everything except the UI layer: ``` packages/ shared/ hooks/ ← useAppRuntime with ChatModelAdapter (shared) tools/ ← tool definitions (shared) web/ components/ ← DOM-based UI terminal/ components/ ← Ink-based terminal UI ``` The runtime hook, tool definitions, and backend code are platform-agnostic. Only the UI components need separate implementations for web and terminal.