Saltar al contenido principal
Versión: 0.13.0

Ink chat

runChatInk() is a full terminal chat built with Ink, modelled on the Claude Code CLI: the reply streams in with its markdown rendered, tool calls appear one by one with their results, checkpoints are panels, and a status bar shows the mode, turns, tokens and context.

function runChatInk(options: Options | SessionOpener, tuiOptions?: InkChatOptions): Promise<void>;

It resolves when the chat ends (an exit command, or Ctrl+C / Ctrl+D at an idle prompt), after closing the session. Without a TTY, or with plain: true, it runs the plain chat with the same options.

Two ways to start it​

With a session opener and a runs folder (recommended): every run gets its own folder, its conversation is kept there, and it can be resumed with --continue or /resume.

await runChatInk((run) => buildSessionOptions(config, run.dir, spec, { run }), {
runsDir: path.resolve(".run"),
});

The opener is called with the run folder to use (a new one, the latest with --continue, or the one picked in /resume) and returns either Options or buildSessionOptions()'s whole result, whose modeControl the chat picks up.

With fixed options: one session, logs wherever you say, no resuming.

const { options, modeControl } = await buildSessionOptions(config, runDir, spec);
await runChatInk(options, { modeControl, sessionLogPath: path.join(runDir, "session.log") });

Options​

Session and files​

OptionTypeDefaultDescription
runsDirstringThe runs folder, with a session opener. See Runs and resuming.
sessionLogPathstringWith fixed options: where to mirror what the terminal shows. With runsDir, each run's session.log is used instead.
historyPathstringThe ↑/↓ history file (JSON lines), kept across runs. Without it, history lasts one session.
historyLimitnumber100Entries kept in the history.
initialPromptstringSent as the first turn before the person types anything (logged, not shown). For an agent that should act at startup. Skipped when resuming a conversation.
exitCommandsstring[]["/exit", "/quit"]Lines that end the chat (case-insensitive).

Look and texts​

OptionTypeDefaultDescription
headerHeaderInfo{ title, fields?, art? }: pinned at the top in full screen, printed once otherwise.
welcomeMessagestringPrinted once at the start (and logged).
promptLabelstring"> "The prompt's label. Leading newlines only reach the log.
agentLabelstringThe label before the agent's reply in the log and the plain chat, e.g. ui.agent("Scout>").
fullscreenbooleanfalseTake the whole terminal. See Full screen.
modeModeShown in the status bar when there's no mode control.
themePartial<Theme>Colors. See Themes.
languagestringThe kit's language. See Languages.
formatAction(toolName, input) => stringcreateFriendlyToolLabel()Tool call labels. See Tool labels.
toolPhrase(toolName) => ToolPhrase | undefinedHow your tools count in a folded summary. See Tool labels.
renderApproval(prompt) => ReactNodeReplaces the preview of the approval panels. See Approval panels.

Behavior​

OptionTypeDefaultDescription
modeControlModeControlWith fixed options: enables Shift+Tab mode switching and shows the live mode.
promptSuggestionsbooleantrueShow the model's predicted next message in the empty prompt (Tab takes it).
firstPromptSuggestionstringThe suggestion before the first turn (the SDK only suggests after a turn).
terminalIntegrationbooleantrueThe tab title, the taskbar progress and /copy. See Terminal integration.
plainbooleanfalseUse the plain readline chat even on a TTY.

What's on screen​

▄▀▀▄ Scout
█ █ mode guided

● Searching the web for "TypeScript 6 release"
⎿ Found 8 results (+7 lines)
● Fetching https://devblogs.microsoft.com/typescript/…
⎿ TypeScript 6.0 is now available…

● TypeScript 6.0 shipped in March. The main changes are:

- **Faster builds** through…

✻ Worked for 12s

╭───────────────────────────────────────────────╮
│ you> █ │
╰───────────────────────────────────────────────╯
⏵⏵ guided (shift+tab) · 3 turns · 45.2k in / 1.3k out · context 8%
  • The agent's reply follows a ●, with its markdown rendered (bold, italics, inline code, lists, headings, quotes, code blocks, and tables as grids), also while it streams.
  • Tool calls show one by one: ● and the tool's label, then ⎿ and the first line of its result (red if it failed, … while it runs). Under a subagent call, the subagent's own tool calls (the latest five). Ctrl+O folds each group of consecutive calls into one summary line ("Read 2 files, ran 1 shell command") and back.
  • The spinner shows what the agent is doing, the seconds, and esc to interrupt; with a subagent working, a second line ↳ shows its action.
  • Each turn ends with ✻ Worked for Ns.
  • The status bar shows the mode ((shift+tab) if it can switch), the turns, the session's input and output tokens, and how full the context window is.

Keyboard​

KeysAction
EnterSend. During a turn the line is queued (shown as queued: …) and sent after.
\ + Enter, Ctrl+JNew line (multi-line message).
TabComplete a /command or an @file, or take the suggestion.
↑ / ↓History. On an empty prompt, ↑ first takes back the last queued line to edit it.
Ctrl+RSearch the history backwards; Ctrl+R again for older matches, Enter takes it, Esc cancels.
Ctrl+W, Ctrl+K, Ctrl+UDelete a word back, to the line end, the whole line.
Ctrl+← / Ctrl+→, Home / End, Ctrl+A / Ctrl+EMove by word, to the start or end.
Delete, BackspaceDelete forward, backward (a pasted block as a whole).
? on an empty promptThe shortcuts panel.
EscInterrupt the turn.
Ctrl+CInterrupt the turn (or answer Stop to a checkpoint); at an idle prompt, leave.
Shift+TabNext mode (guided ↔ interactive).
Ctrl+OFold or unfold tool calls.

Pasting a multi-line block folds it into a token, [Pasted text #1 +12 lines], sent expanded. @ followed by part of a path suggests the project's files (Tab completes).

Local commands​

CommandWhat it does
/exit, /quitLeave (configurable with exitCommands).
/copyCopy the last reply to the clipboard (OSC 52), with terminal integration on.
/resumePick an earlier conversation to resume, with a runs folder.

Any other /command is checked against the session's commands (skills and plugin commands included): an unknown one shows "Unknown command" instead of reaching the model as text.

Full screen​

With fullscreen: true the chat takes the terminal's alternate screen:

  • the header stays pinned at the top, the prompt and the status bar at the bottom;
  • the history scrolls in its own view: PageUp/PageDown and the mouse wheel move it, Ctrl+End or typing goes back to the bottom, and output arriving while you're scrolled up doesn't move it (the status bar shows ↓ N more lines (Ctrl+End));
  • dragging selects text; a right-click copies the selection to the clipboard and clears it, as in Windows Terminal;
  • the terminal is cleared when the chat ends.

Without it the chat is inline: the history goes to the terminal's own scrollback, and the header is printed once at the start.

Approval panels​

Checkpoints are panels in the chat (see Human in the loop). Replace the preview (title and lines) with your own rendering; the choices stay:

import { Box, Text } from "ink";
import type { RenderApproval } from "@falkenslab/agent-kit";

const renderApproval: RenderApproval = (prompt) => (
<Box flexDirection="column">
<Text bold color="yellow">{prompt.title}</Text>
{prompt.lines.map((line, i) => (
<Text key={i} wrap="truncate-end">{line}</Text>
))}
</Box>
);

await runChatInk(opener, { runsDir, renderApproval });

Terminal integration​

On by default (terminalIntegration: false turns it off):

  • the tab title is the header's title, and ✻ <title> — working… while a turn runs (OSC 0);
  • the taskbar button shows indeterminate progress during a turn (Windows Terminal, OSC 9;4);
  • when a turn ends while the window isn't focused, the taskbar button turns yellow until the window is focused again (focus reporting);
  • /copy copies the last reply (OSC 52).

Terminals that don't understand these sequences ignore them.

The session log​

Everything the plain console would print is mirrored to the session log: the welcome message, each line typed, the replies, [action] lines, notices and errors, without colors. The screen and the log are drawn differently (the log is plain text), but tell the same story.

header: {
title: "Captain Whiskers",
fields: { mode: "guided", workspace: "course-2026" },
art: [
pc.gray(" ▄▄███▄▄"),
pc.gray(" ▀▀▀▀▀▀▀▀▀"),
pc.yellow(" /\\_/\\"),
pc.yellow(" ( o.█ )"),
pc.yellow(" > ^ <"),
],
},

art is drawn left of the title, one string per row, colored as you like. Use single-column characters only (ASCII, box and block characters such as ▄ ▀ █): emoji are measured differently by the kit and by terminals, and would shift the title.