ink by vercel-labs/json-render
npx skills add https://github.com/vercel-labs/json-render --skill 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";
// 使用标准 + 自定义组件创建目录
const catalog = defineCatalog(schema, {
components: {
...standardComponentDefinitions,
CustomWidget: {
props: z.object({ title: z.string() }),
slots: [],
description: "自定义部件",
},
},
actions: standardActionDefinitions,
});
// 仅注册自定义组件(标准组件已内置)
const { registry } = defineRegistry(catalog, {
components: {
CustomWidget: ({ props }) => <Text>{props.title}</Text>,
} as Components<typeof catalog>,
});
// 渲染
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": "仪表板", "level": "h1" },
"children": []
},
"content": {
"type": "Text",
"props": { "text": "来自终端的问候!" },
"children": []
}
}
}
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
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 - 带有可选标签的可点击 URLStatusLine - 带有彩色图标的状态消息(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" } - 双向绑定:用于表单组件的自然值属性{ "$bindItem": "field" } - 与重复项字段的双向绑定{ "$cond":<condition>, "$then": <value>, "$else": <value> } - 条件值{ "$template": "Hello, ${/name}!" } - 将状态值插入到字符串中组件不使用 statePath 属性进行双向绑定。请改为在自然值属性上使用 { "$bindState": "/path" }。
组件使用 emit 来触发命名事件。元素的 on 字段将事件映射到操作绑定:
CustomButton: ({ props, emit }) => (
<Box>
<Text>{props.label}</Text>
{/* emit("press") 触发规范中 on.press 绑定的操作 */}
</Box>
),
{
"type": "CustomButton",
"props": { "label": "提交" },
"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 } 获取当前索引。
使用 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: "你是一个终端助手。" });
| 提供者 | 用途 |
|---|---|
StateProvider | 跨组件共享状态(JSON 指针路径)。接受可选的 store 属性用于受控模式。 |
ActionProvider | 处理通过事件系统分发的操作 |
VisibilityProvider | 启用基于状态的条件渲染 |
ValidationProvider | 表单字段验证 |
FocusProvider | 管理交互式组件之间的焦点 |
JSONUIProvider | 所有上下文的组合提供者 |
将 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 会自动重新渲染
当提供 store 时,initialState 和 onStateChange 将被忽略。
import { createRenderer } from "@json-render/ink";
import { standardComponents } from "@json-render/ink";
import { catalog } from "./catalog";
const InkRenderer = createRenderer(catalog, {
...standardComponents,
// 自定义组件覆盖写在这里
});
// InkRenderer 包含所有提供者(状态、可见性、操作、焦点)
render(
<InkRenderer spec={spec} state={{ activeTab: "overview" }} />
);
| 导出 | 用途 |
|---|---|
defineRegistry | 从目录创建类型安全的组件注册表 |
Renderer | 使用注册表渲染规范 |
createRenderer | 高级:创建带有内置提供者的组件 |
JSONUIProvider | 所有上下文的组合提供者 |
schema | Ink 扁平元素映射模式(包括内置状态操作) |
standardComponentDefinitions | 所有标准组件的目录定义 |
standardActionDefinitions | 标准操作的目录定义 |
standardComponents | 预构建的组件实现 |
useStateStore | 访问状态上下文 |
useStateValue | 从状态获取单个值 |
useBoundProp | 用于 $bindState/$bindItem 表达式的双向绑定 |
useActions | 访问操作上下文 |
useAction | 获取单个操作分发函数 |
useOptionalValidation | useValidation 的非抛出变体 |
useUIStream | 从 API 端点流式传输规范 |
createStateStore | 创建与框架无关的内存 StateStore |
StateStore | 用于插入外部状态管理的接口 |
Components | 类型化组件映射(目录感知) |
Actions | 类型化操作映射(目录感知) |
ComponentContext | 类型化组件上下文(目录感知) |
flatToTree | 将扁平元素映射转换为树结构 |
每周安装量
235
仓库
GitHub 星标
13.3K
首次出现
2 天前
安全审计
安装于
codex231
gemini-cli229
cursor229
opencode229
kimi-cli228
amp228
Ink terminal renderer that converts JSON specs into interactive terminal component trees with standard components, data binding, visibility, actions, and dynamic props.
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>
);
}
The Ink schema uses a flat element map with a root key:
{
"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 layout container (like a terminal <div>). Use for grouping, spacing, borders, alignment. Default flexDirection is row.Text - Text output with optional styling (color, bold, italic, etc.)Newline - Inserts blank lines. Must be inside a Box with flexDirection column.Spacer - Flexible empty space that expands along the main axis.Heading - Section heading (h1: bold+underlined, h2: bold, h3: bold+dimmed, h4: dimmed)Divider - Horizontal separator with optional centered titleBadge - Colored inline label (variants: default, info, success, warning, error)Spinner - Animated loading spinner with optional labelProgressBar - Horizontal progress bar (0-1)Sparkline - Inline chart using Unicode block charactersBarChart - Horizontal bar chart with labels and valuesTable - Tabular data with headers and rowsList - Bulleted or numbered listTextInput - Text input field (events: submit, change)Select - Selection menu with arrow key navigation (events: change)MultiSelect - Multi-selection with space to toggle (events: change, submit)ConfirmInput - Yes/No confirmation prompt (events: confirm, deny)Tabs - Tab bar navigation with left/right arrow keys (events: change)Use visible on elements to show/hide based on state. Syntax: { "$state": "/path" }, { "$state": "/path", "eq": value }, { "$state": "/path", "not": true }, { "$and": [cond1, cond2] } for AND, { "$or": [cond1, cond2] } for OR.
Any prop value can be a data-driven expression resolved at render time:
{ "$state": "/state/key" } - reads from state model (one-way read){ "$bindState": "/path" } - two-way binding: use on the natural value prop of form components{ "$bindItem": "field" } - two-way binding to a repeat item field{ "$cond":<condition>, "$then": <value>, "$else": <value> } - conditional value{ "$template": "Hello, ${/name}!" } - interpolates state values into stringsComponents do not use a statePath prop for two-way binding. Use { "$bindState": "/path" } on the natural value prop instead.
Components use emit to fire named events. The element's on field maps events to action bindings:
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, and removeState are built-in and handled automatically:
{ "action": "setState", "params": { "statePath": "/activeTab", "value": "home" } }
{ "action": "pushState", "params": { "statePath": "/items", "value": { "text": "New" } } }
{ "action": "removeState", "params": { "statePath": "/items", "index": 0 } }
Use the repeat field on a container element to render items from a state array:
{
"type": "Box",
"props": { "flexDirection": "column" },
"repeat": { "statePath": "/items", "key": "id" },
"children": ["item-row"]
}
Inside repeated children, use { "$item": "field" } to read from the current item and { "$index": true } for the current index.
Use useUIStream to progressively render specs from JSONL patch streams:
import { useUIStream } from "@json-render/ink";
const { spec, send, isStreaming } = useUIStream({ api: "/api/generate" });
Use the ./server export to generate AI system prompts from your catalog:
import { catalog } from "./catalog";
const systemPrompt = catalog.prompt({ system: "You are a terminal assistant." });
| Provider | Purpose |
|---|---|
StateProvider | Share state across components (JSON Pointer paths). Accepts optional store prop for controlled mode. |
ActionProvider | Handle actions dispatched via the event system |
VisibilityProvider | Enable conditional rendering based on state |
ValidationProvider | Form field validation |
FocusProvider | Manage focus across interactive components |
Pass a StateStore to StateProvider (or JSONUIProvider) to use external state management:
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
When store is provided, initialState and onStateChange are ignored.
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" }} />
);
| Export | Purpose |
|---|---|
defineRegistry | Create a type-safe component registry from a catalog |
Renderer | Render a spec using a registry |
createRenderer | Higher-level: creates a component with built-in providers |
JSONUIProvider | Combined provider for all contexts |
schema | Ink flat element map schema (includes built-in state actions) |
standardComponentDefinitions |
Weekly Installs
235
Repository
GitHub Stars
13.3K
First Seen
2 days ago
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex231
gemini-cli229
cursor229
opencode229
kimi-cli228
amp228
ListItem - Structured list row with title, subtitle, leading/trailing textCard - Bordered container with optional titleKeyValue - Key-value pair displayLink - Clickable URL with optional labelStatusLine - Status message with colored icon (info, success, warning, error)Markdown - Renders markdown text with terminal stylingJSONUIProvider |
| Combined provider for all contexts |
| Catalog definitions for all standard components |
standardActionDefinitions | Catalog definitions for standard actions |
standardComponents | Pre-built component implementations |
useStateStore | Access state context |
useStateValue | Get single value from state |
useBoundProp | Two-way binding for $bindState/$bindItem expressions |
useActions | Access actions context |
useAction | Get a single action dispatch function |
useOptionalValidation | Non-throwing variant of useValidation |
useUIStream | Stream specs from an API endpoint |
createStateStore | Create a framework-agnostic in-memory StateStore |
StateStore | Interface for plugging in external state management |
Components | Typed component map (catalog-aware) |
Actions | Typed action map (catalog-aware) |
ComponentContext | Typed component context (catalog-aware) |
flatToTree | Convert flat element map to tree structure |