agentskit.js
Chat UIs

@agentskit/ink

Terminal chat UI built with Ink. Uses the same @agentskit/core controller as @agentskit/react, so the chat logic is identical — only the rend

Terminal chat UI built with Ink. Uses the same @agentskit/core controller as @agentskit/react, so the chat logic is identical — only the renderer differs.

When to use

  • CLI-style or SSH-friendly chat without a browser.
  • You want parity with useChat from React but with terminal rendering.

Use @agentskit/react for web; use the CLI for zero-code terminal chat.

Install

npm install @agentskit/ink @agentskit/core ink react
# optional: real AI providers
npm install @agentskit/adapters

Hook

useChat

Identical API to @agentskit/react's useChat. The same ChatReturn object is returned.

import { useChat } from '@agentskit/ink'

const chat = useChat({
  adapter: myAdapter,
  systemPrompt: 'You are...',
})

See the useChat reference for the full return type.

Complete example (demo adapter — no API key needed)

import React from 'react'
import { render, Box, Text } from 'ink'
import {
  ChatContainer,
  Message,
  InputBar,
  ThinkingIndicator,
  useChat,
} from '@agentskit/ink'
import type { AdapterFactory } from '@agentskit/ink'

function createDemoAdapter(): AdapterFactory {
  return {
    createSource: ({ messages }) => {
      let cancelled = false
      return {
        stream: async function* () {
          const last = [...messages].reverse().find(m => m.role === 'user')
          const reply = `You said: "${last?.content ?? ''}". This is a demo response.`
          for (const chunk of reply.match(/.{1,20}/g) ?? []) {
            if (cancelled) return
            await new Promise(r => setTimeout(r, 45))
            yield { type: 'text' as const, content: chunk }
          }
          yield { type: 'done' as const }
        },
        abort: () => { cancelled = true },
      }
    },
  }
}

function App() {
  const chat = useChat({
    adapter: createDemoAdapter(),
    systemPrompt: 'You are a helpful terminal assistant.',
  })

  return (
    <Box flexDirection="column" gap={1}>
      <Text bold color="cyan">AgentsKit Terminal Chat</Text>
      <ChatContainer>
        {chat.messages.map(msg => (
          <Message key={msg.id} message={msg} />
        ))}
      </ChatContainer>
      <ThinkingIndicator visible={chat.status === 'streaming'} />
      <InputBar chat={chat} placeholder="Type and press Enter..." />
    </Box>
  )
}

render(<App />)

Swap to a real provider

import { anthropic } from '@agentskit/adapters'

const chat = useChat({
  adapter: anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, model: 'claude-sonnet-4-6' }),
})

Keyboard navigation

InputBar uses Ink's useInput hook. The following keys are handled automatically:

KeyAction
Any characterAppended to input
EnterSend message
Backspace / DeleteRemove last character
Ctrl+CExit (Ink default)

Input is disabled while chat.status === 'streaming'.

Terminal colours

Message applies a fixed colour per role using Ink's color prop:

RoleColour
assistantcyan
usergreen
systemyellow
toolmagenta

ToolCallView renders in a rounded box with magenta text. ThinkingIndicator renders in yellow.

Differences from @agentskit/react

Feature@agentskit/react@agentskit/ink
RendererDOMInk (terminal)
Theme / CSSdata-ak-* + CSS variablesTerminal colours
Markdown componentYesNo
CodeBlock componentYesNo
useStream hookYesNo
useReactive hookYesNo
InputBar multilineShift+EnterNo (single line)

Troubleshooting

IssueMitigation
Raw mode / key issuesEnsure stdout is a TTY; avoid piping when debugging input.
Layout overflowNarrow terminals clip long lines; prefer shorter system prompts or external pager for dumps.
Missing hooksuseStream / useReactive are not bundled in Ink — import patterns from @agentskit/react only apply where those hooks exist.

See also

Start here · Packages · TypeDoc (@agentskit/ink) · React · Components · @agentskit/core

✎ Edit this page on GitHub·Found a problem? Open an issue →·How to contribute →

On this page