# Getting Started
URL: /docs/ink

Build AI chat interfaces for the terminal with @assistant-ui/react-ink.

Quick Start \[#quick-start]

The fastest way to get started with assistant-ui for the terminal.

<Steps>
  <Step>
    Create a new project \[#create-a-new-project]

    ```sh
    npx assistant-ui@latest create --ink my-chat-app
    cd my-chat-app
    ```
  </Step>

  <Step>
    Install dependencies \[#install-dependencies]

    ```sh
    pnpm install
    ```
  </Step>

  <Step>
    Start the app \[#start-the-app]

    ```sh
    pnpm dev
    ```
  </Step>
</Steps>

Manual Setup \[#manual-setup]

If you prefer to add assistant-ui to an existing Node.js project, follow these steps.

<Steps>
  <Step>
    Install dependencies \[#install-dependencies-1]

    ```sh
    npm install @assistant-ui/react-ink @assistant-ui/react-ink-markdown ink react
    ```
  </Step>

  <Step>
    Create a chat model adapter \[#create-a-chat-model-adapter]

    Define how your app communicates with your AI backend. This example uses a simple streaming adapter:

    ```ts title="adapters/my-chat-adapter.ts"
    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 }] };
        }
      },
    };
    ```

    <Callout type="info">
      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.
    </Callout>
  </Step>

  <Step>
    Set up the runtime \[#set-up-the-runtime]

    ```tsx title="app.tsx"
    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>
      );
    }
    ```
  </Step>

  <Step>
    Build your chat UI \[#build-your-chat-ui]

    Use primitives to compose your terminal chat interface:

    ```tsx title="components/thread.tsx"
    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>
      );
    };
    ```
  </Step>

  <Step>
    Render with Ink \[#render-with-ink]

    ```tsx title="index.tsx"
    import { render } from "ink";
    import { App } from "./app.js";

    render(<App />);
    ```
  </Step>
</Steps>

What's Next? \[#whats-next]

<Cards>
  <Card title="Migration from Web" description="Already using assistant-ui? Migrate your web app to the terminal." href="/docs/ink/migration" />

  <Card title="Custom Backend" description="Connect to your own backend API or manage threads server-side." href="/docs/ink/custom-backend" />

  <Card title="Primitives" description="Composable terminal UI components for building chat interfaces." href="/docs/ink/primitives" />

  <Card title="Example App" description="Full terminal chat example with markdown rendering and streaming." href="https://github.com/assistant-ui/assistant-ui/tree/main/examples/with-react-ink" />
</Cards>
