mcpField
I
ink

ink

github.com/assistant-ui开发29997 次安装2 天前更新
代码仓库

关于 ink

ink 是用于通过 Ink 在终端构建助手聊天界面的 assistant-ui 技能与运行时软件包。它复用 assistant-ui 的运行时、工具、状态和 AI SDK 传输,同时使用终端专用原语与 Markdown 渲染。

主要功能

  1. 01基于 Ink 的终端界面原语
  2. 02共享 assistant-ui 运行时与状态
  3. 03适用于终端的消息部分渲染器
  4. 04受控的撰写框文本输入
  5. 05ANSI 样式 Markdown 输出
  6. 06文件存储与会话适配器

适用场景

  1. 01在 Node 项目中构建助手聊天命令行界面
  2. 02将助手 Markdown 渲染为终端输出
  3. 03将会话数据保存在本地文件中

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/assistant-ui/skills --skill ink

安装后在 Claude Code 中输入/ink确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
adapters.md
custom-backend.md
hooks.md
migration.md
primitives.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。

参考资料

启动终端应用

从头开始时,请使用终端示例搭建项目:

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 需要绝对 api URL,例如 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 渲染器和源代码显示

评论

支持 Markdown