关于 ink
ink 是用于通过 Ink 在终端构建助手聊天界面的 assistant-ui 技能与运行时软件包。它复用 assistant-ui 的运行时、工具、状态和 AI SDK 传输,同时使用终端专用原语与 Markdown 渲染。
主要功能
- 01基于 Ink 的终端界面原语
- 02共享 assistant-ui 运行时与状态
- 03适用于终端的消息部分渲染器
- 04受控的撰写框文本输入
- 05ANSI 样式 Markdown 输出
- 06文件存储与会话适配器
适用场景
- 01在 Node 项目中构建助手聊天命令行界面
- 02将助手 Markdown 渲染为终端输出
- 03将会话数据保存在本地文件中
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ assistant-ui/ skills --skill ink安装后在 Claude Code 中输入/ink确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/adapters.mdcustom-backend.mdhooks.mdmigration.mdprimitives.md详细介绍
assistant-ui Ink
请始终查阅 assistant-ui.com/llms.txt 获取最新 API。
@assistant-ui/react-ink 将 assistant-ui 运行时连接到 Ink 的终端渲染器。它与 Web 应用共享运行时、工具、状态和 AI SDK 传输,但不使用 DOM、CSS 或复制的元素。使用 Ink 的 Box 和 Text 以及支持运行时的原语来构建屏幕。@assistant-ui/react-ink-markdown 会将助手文本渲染为 ANSI 样式的终端 Markdown。
参考资料
- ./references/primitives.md -- 所有终端原语命名空间、其组成部分、作用域上下文以及受控的
TextInput - ./references/hooks.md -- 运行时、状态、工具、通知、语音和清单钩子
- ./references/adapters.md -- 文件存储、附件和标题适配器
- ./references/custom-backend.md -- 本地推理、本地磁盘持久化和后端线程所有权
- ./references/migration.md -- Web 应用中哪些内容可以迁移,以及哪些内容必须为 Ink 重新构建
启动终端应用
从头开始时,请使用终端示例搭建项目:
npx assistant-ui@latest create --ink my-app
cd my-app
--ink 会解析为 with-react-ink CLI 示例。示例列表中也包含 with-react-ink-web,但它不是 --ink 的搭建目标。
对于现有的 Node 项目,请同时安装运行时、终端渲染器和 AI SDK 传输:
npm install @assistant-ui/react-ink @assistant-ui/react-ink-markdown ink react @assistant-ui/ai-sdk
API 路由必须运行在单独的后端项目中。与浏览器应用不同,终端进程无法使用相对的 /api/chat URL。请为 AssistantChatTransport 提供进程可访问的完整 URL。
import { AssistantRuntimeProvider } from "@assistant-ui/react-ink";
import { AssistantChatTransport, useChatRuntime } from "@assistant-ui/ai-sdk";
import { Box } from "ink";
import { TerminalThread } from "./components/terminal-thread.js";
const CHAT_API_URL = "http://localhost:3000/api/chat";
export function App() {
const runtime = useChatRuntime({
transport: new AssistantChatTransport({ api: CHAT_API_URL }),
});
return (
<AssistantRuntimeProvider runtime={runtime}>
<Box flexDirection="column">
<TerminalThread />
</Box>
</AssistantRuntimeProvider>
);
}
useChatRuntime 和 AssistantChatTransport 来自 @assistant-ui/ai-sdk。其后端使用 AI SDK v7 UI 消息流。有关路由结构,请参阅运行时的 AI SDK v7 指南。
构建终端线程
ThreadPrimitive.Messages 会在每个子组件运行前创建当前消息作用域。在消息组件中读取 s.message,然后使用 Ink 原语而不是 Web 元素。使用 AuiIf 判断运行时状态,并使用 LoadingPrimitive 显示正在进行的运行。
import { Box, Text } from "ink";
import {
AuiIf,
ComposerPrimitive,
LoadingPrimitive,
ThreadPrimitive,
useAuiState,
} from "@assistant-ui/react-ink";
import { MarkdownText } from "@assistant-ui/react-ink-markdown";
function Message() {
const message = useAuiState((s) => s.message);
const text = message.content
.filter((part) => part.type === "text")
.map((part) => ("text" in part ? part.text : ""))
.join("");
if (message.role === "user") {
return <Text color="green">You: {text}</Text>;
}
return (
<Box flexDirection="column" marginBottom={1}>
<Text color="blue">Assistant:</Text>
<MarkdownText text={text} />
</Box>
);
}
export function TerminalThread() {
return (
<ThreadPrimitive.Root flexDirection="column">
<AuiIf condition={(s) => s.thread.isEmpty}>
<Text dimColor>Send a message to begin.</Text>
</AuiIf>
<ThreadPrimitive.Messages>{() => <Message />}</ThreadPrimitive.Messages>
<LoadingPrimitive.Root gap={1}>
<LoadingPrimitive.Spinner variant="bar" />
<LoadingPrimitive.Text />
<LoadingPrimitive.ElapsedTime />
</LoadingPrimitive.Root>
<Box borderStyle="round" borderColor="gray" paddingX={1}>
<Text color="gray">{"> "}</Text>
<ComposerPrimitive.Input submitOnEnter placeholder="Message..." autoFocus />
</Box>
</ThreadPrimitive.Root>
);
}
这个简单的消息示例有意只渲染文本部分。如果需要工具调用、附件、数据、来源或推理,请使用 MessagePrimitive.Parts。其默认的终端安全渲染器会处理这些部分类型,原语中展示了组件映射。
终端输入行为
ComposerPrimitive.Input 是一个绑定到 composer 的 TextInput。它是受控的行编辑器,并非 Ink 中不存在的原生输入框。submitOnEnter 会发送当前 composer 文本。启用 multiLine 后,按 Enter 会插入换行符;如果启用了 submitOnEnter,则在终端能够区分时,按 Shift Enter 会插入换行符。Ctrl J 仅在多行模式下插入换行符,单行输入时绝不会发送内容。
- Left、Right、Backspace、Delete 和
Ctrl D都以一个字素为单位操作,因此不会拆分 emoji、ZWJ 序列、组合字符或 CJK 表意文字。 - 在单行模式下,Home 和 End 会选中整个缓冲区;在多行模式下,则会选中当前行。
Ctrl A和Ctrl E始终会选中当前行的边界。 - Up 和 Down 会在多行中移动,同时保留终端显示列;显示列根据字素宽度而非 UTF 16 偏移量计算。
Ctrl W、Alt B、Alt F和Alt D会按Intl.Segmenter的词边界进行移动或删除。Ctrl U和Ctrl K会删除到当前边界。在多行行尾,Ctrl K会合并下一行。
Meta 绑定需要终端发出以 Escape 为前缀的序列。在 macOS Terminal 中,为 Alt 绑定启用“Use Option as Meta key”。Shift Enter 需要支持 CSI u,包括 iTerm2 3.4 或更高版本、kitty 和 foot。其他终端会将其视为 Enter,并采用 submitOnEnter 行为。若要在 composer 之外使用 TextInput,请参阅 primitives。
终端中的 Markdown
将累积的文本传递给来自 @assistant-ui/react-ink-markdown 的 MarkdownText。它会生成终端样式的输出,而非 HTML 或 React DOM 树。该包还导出了 MarkdownTextPrimitive、useShikiHighlighter 和用于自定义渲染器的主题类型;除非需要更改其渲染流程,否则请从 MarkdownText 开始使用。
import { MarkdownText } from "@assistant-ui/react-ink-markdown";
export function AssistantReply({ text }: { text: string }) {
return <MarkdownText text={text} />;
}
常见问题
终端应用将请求发送到自身进程,而非后端
AssistantChatTransport需要绝对apiURL,例如http://localhost:3000/api/chat。请将 AI SDK 路由托管在 Ink 进程之外。
Web Thread 或 shadcn 元素在 CLI 中没有任何渲染结果
- 浏览器元素需要 DOM 和 CSS。请使用 Ink
Box、Text和@assistant-ui/react-ink基元重新构建界面。
基元或状态钩子因缺少运行时上下文而报错
- 将其挂载在
AssistantRuntimeProvider runtime={runtime}下。ThreadPrimitive.Messages和MessagePrimitive.Parts也会创建其子元素读取的项作用域。
按 Enter 不会发送内容,或意外插入换行符
- 默认情况下,
submitOnEnter为 false。只有在需要按 Enter 发送内容、并在支持的终端中按 Shift Enter 插入换行符时,才将它与multiLine搭配使用。
删除 emoji 会破坏可见输入,或垂直导航会跳到错误位置
- 使用
ComposerPrimitive.Input或导出的TextInput。它们的缓冲区使用字素分段和终端显示宽度。请勿改用字符索引运算。
重启 CLI 后,Thread 数据消失
useLocalRuntime使用的是进程内存。单个本地进程可使用createFileStorageAdapter,后端拥有的元数据则可使用RemoteThreadListAdapter。
Markdown 是从 Web 包导入的
- 请从
@assistant-ui/react-ink-markdown导入MarkdownText,它会生成终端格式。Web Markdown 渲染器面向 DOM。
相关技能
- setup -- CLI 脚手架、浏览器设置和软件包安装
- runtime -- 共享的 assistant-ui 运行时、状态、Thread 和传输设计
- primitives -- 目标不是终端时使用的浏览器无样式基元
- tools -- 同样适用于 Ink 的工具包定义和工具调用渲染器
- markdown -- 浏览器 Markdown 渲染器和源代码显示