mcpField
I
ink

ink

github.com/vercel-labs开发18.8k1.3k 次安装5 天前更新
代码仓库

关于 ink

@json-render/ink 将 JSON 规格渲染为交互式终端组件树,面向终端界面开发者,结合标准组件、状态绑定、事件、操作和流式规格。

主要功能

  1. 01在终端渲染 JSON 规格
  2. 02提供标准布局与内容组件
  3. 03支持交互输入和导航
  4. 04支持状态绑定与条件显示
  5. 05流式处理 JSONL 规格补丁

适用场景

  1. 01根据 JSON 规格构建终端界面
  2. 02创建有状态的终端表单和菜单
  3. 03逐步渲染流式 UI 规格

安装

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

$npx skills add https://github.com/vercel-labs/json-render --skill ink

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

详细介绍

@json-render/ink

Ink 终端渲染器,可将 JSON 规范转换为交互式终端组件树,支持标准组件、数据绑定、可见性、操作和动态属性。

快速开始

import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/ink/schema";
import {
  standardComponentDefinitions,
  standardActionDefinitions,
} from "@json-render/ink/catalog";
import { defineRegistry, Renderer, type Components } from "@json-render/ink";
import { z } from "zod";

// Create catalog with standard + custom components
const catalog = defineCatalog(schema, {
  components: {
    ...standardComponentDefinitions,
    CustomWidget: {
      props: z.object({ title: z.string() }),
      slots: [],
      description: "Custom widget",
    },
  },
  actions: standardActionDefinitions,
});

// Register only custom components (standard ones are built-in)
const { registry } = defineRegistry(catalog, {
  components: {
    CustomWidget: ({ props }) => <Text>{props.title}</Text>,
  } as Components<typeof catalog>,
});

// Render
function App({ spec }) {
  return (
    <JSONUIProvider initialState={{}}>
      <Renderer spec={spec} registry={registry} />
    </JSONUIProvider>
  );
}

规范结构(扁平元素映射)

Ink 模式使用带有根键的扁平元素映射:

{
  "root": "main",
  "elements": {
    "main": {
      "type": "Box",
      "props": { "flexDirection": "column", "padding": 1 },
      "children": ["heading", "content"]
    },
    "heading": {
      "type": "Heading",
      "props": { "text": "Dashboard", "level": "h1" },
      "children": []
    },
    "content": {
      "type": "Text",
      "props": { "text": "Hello from the terminal!" },
      "children": []
    }
  }
}

标准组件

布局
  • Box - Flexbox 布局容器(类似终端中的 <div>)。用于分组、间距、边框和对齐。默认 flexDirection 为 row。
  • Text - 文本输出,可选样式(颜色、粗体、斜体等)
  • Newline - 插入空行。必须放在 flexDirection 为 column 的 Box 中。
  • Spacer - 可扩展的空白区域,沿主轴扩展。
内容
  • Heading - 分节标题(h1:粗体+下划线,h2:粗体,h3:粗体+暗淡,h4:暗淡)
  • Divider - 水平分隔线,可选居中标题
  • Badge - 彩色行内标签(变体:default、info、success、warning、error)
  • Spinner - 动画加载指示器,可选标签
  • ProgressBar - 水平进度条(0-1)
  • Sparkline - 使用 Unicode 块字符绘制的行内图表
  • BarChart - 带标签和值的水平条形图
  • Table - 含表头和行的表格数据
  • List - 项目符号列表或编号列表
  • ListItem - 结构化列表行,包含标题、副标题、前置文本和后置文本
  • Card - 带边框的容器,可选标题
  • KeyValue - 键值对显示
  • Link - 可点击的 URL,可选标签
  • StatusLine - 带彩色图标的状态消息(info、success、warning、error)
  • Markdown - 使用终端样式渲染 Markdown 文本
交互组件
  • TextInput - 文本输入字段(事件:submit、change)
  • Select - 可用方向键导航的选择菜单(事件:change)
  • MultiSelect - 按空格键切换的多选菜单(事件:change、submit)
  • ConfirmInput - 是/否确认提示词(事件:confirm、deny)
  • Tabs - 使用左右方向键导航的标签栏(事件:change)

可见性条件

在元素上使用 visible,根据状态显示或隐藏元素。语法:{ "$state": "/path" }、{ "$state": "/path", "eq": value }、{ "$state": "/path", "not": true };{ "$and": [cond1, cond2] } 表示 AND,{ "$or": [cond1, cond2] } 表示 OR。

动态属性表达式

任何属性值都可以是由数据驱动、在渲染时解析的表达式:

  • { "$state": "/state/key" } - 从状态模型读取(单向读取)
  • { "$bindState": "/path" } - 双向绑定:用于表单组件的自然 value 属性
  • { "$bindItem": "field" } - 双向绑定到重复项字段
  • { "$cond": <condition>, "$then": <value>, "$else": <value> } - 条件值
  • { "$template": "Hello, ${/name}!" } - 将状态值插入字符串

组件不使用 statePath 属性进行双向绑定。应在自然 value 属性上使用 { "$bindState": "/path" }。

事件系统

组件使用 emit 触发具名事件。元素的 on 字段将事件映射到操作绑定:

CustomButton: ({ props, emit }) => (
  <Box>
    <Text>{props.label}</Text>
    {/* emit("press") triggers the action bound in the spec's on.press */}
  </Box>
),
{
  "type": "CustomButton",
  "props": { "label": "Submit" },
  "on": { "press": { "action": "submit" } },
  "children": []
}

内置操作

setState、pushState 和 removeState 是内置操作,会自动处理:

{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }

重复(动态列表)

在容器元素上使用 repeat 字段,从状态数组中渲染项目:

{
  "type": "Box",
  "props": { "flexDirection": "column" },
  "repeat": { "statePath": "/items", "key": "id" },
  "children": ["item-row"]
}

在重复项的子元素中,使用 { "$item": "field" } 读取当前项目,使用 { "$index": true } 获取当前索引。

对于嵌套列表,内部 repeat 可以使用 { "statePath": { "$item": "children" } },遍历外层项目中的数组。

流式传输

使用 useUIStream,从 JSONL 补丁流逐步渲染规范:

import { useUIStream } from "@json-render/ink";

const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });

服务端提示词生成

使用 ./server 导出项,根据目录生成 AI 系统提示词:

import { catalog } from "./catalog";

const systemPrompt = catalog.prompt({ system: "You are a terminal assistant." });

Providers

ProviderPurpose
StateProvider在组件之间共享状态(使用 JSON Pointer 路径)。接受可选的 store 属性以启用受控模式。
ActionProvider处理通过事件系统分发的操作
VisibilityProvider基于状态启用条件渲染
ValidationProvider表单字段验证
FocusProvider管理交互组件之间的焦点
JSONUIProvider组合所有上下文的 Provider
外部 Store(受控模式)

将 StateStore 传递给 StateProvider(或 JSONUIProvider),即可使用外部状态管理:

import { createStateStore, type StateStore } from "@json-render/ink";

const store = createStateStore({ count: 0 });

<StateProvider store={store}>{children}</StateProvider>

store.set("/count", 1); // React re-renders automatically

提供 store 时,initialState 和 onStateChange 会被忽略。

createRenderer(高层 API)

import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";

const InkRenderer = createRenderer(catalog, {
  ...standardComponents,
  // custom component overrides here
});

// InkRenderer includes all providers (state, visibility, actions, focus)
render(
  <InkRenderer spec={spec} state={{ activeTab: "overview" }} />
);

主要导出

导出用途
defineRegistry基于 catalog 创建类型安全的组件注册表
Renderer使用注册表渲染 spec
createRenderer高层 API:创建带有内置 Provider 的组件
JSONUIProvider组合所有上下文的 Provider
schemaInk 扁平元素映射的 schema(包含内置的状态操作)
standardComponentDefinitions所有标准组件的 catalog 定义
standardActionDefinitions标准操作的 catalog 定义
standardComponents预构建的组件实现
useStateStore访问状态上下文
useStateValue获取状态中的单个值
useBoundProp为 $bindState/$bindItem 表达式提供双向绑定
useActions访问操作上下文
useAction获取单个操作分发函数
useOptionalValidationuseValidation 的非抛错变体
useUIStream从 API 端点流式接收 spec
createStateStore创建与框架无关的内存 StateStore
StateStore用于接入外部状态管理的接口
Components带类型的组件映射(感知 catalog)
Actions带类型的操作映射(感知 catalog)
ComponentContext带类型的组件上下文(感知 catalog)
flatToTree将扁平元素映射转换为树结构

终端 UI 设计指南

  • 使用 Box 进行布局(flexDirection、padding、gap)。默认 flexDirection 为 row。
  • 终端宽度约为 80-120 列。主结构优先使用纵向布局(flexDirection: column)。
  • 在 Box 上使用 borderStyle 进行视觉分组(single、double、round、bold)。
  • 使用具名终端颜色:red、green、yellow、blue、magenta、cyan、white、gray。
  • 使用 Heading 作为分区标题,Divider 分隔各分区,Badge 显示状态,KeyValue 显示带标签的数据,Card 用于带边框的分组。
  • 在多视图 UI 中使用 Tabs,并为子内容设置 visible 条件。
  • 使用 Sparkline 展示行内趋势,使用 BarChart 比较数值。

评论

支持 Markdown