mcpField
I
ink

ink

github.com/vercel-labs開発18.8k1.3k 回インストール5 日前に更新
リポジトリ

ink について

@json-render/ink は JSON 仕様をインタラクティブなターミナルコンポーネントツリーとして描画します。ターミナル UI の開発者向けで、標準コンポーネントに状態バインディング、イベント、アクション、仕様のストリーミングを組み合わせます。

主な機能

  1. 01JSON 仕様をターミナルに描画
  2. 02標準のレイアウト・コンテンツコンポーネント
  3. 03インタラクティブな入力とナビゲーション
  4. 04状態バインディングと条件付き表示
  5. 05JSONL 仕様パッチのストリーミング

活用シーン

  1. 01JSON 仕様からターミナル UI を構築
  2. 02状態を持つターミナルフォームやメニューの作成
  3. 03ストリーミング UI 仕様の段階的な描画

インストール

コマンドをコピーしてターミナルで実行します。

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

インストール後、Claude Code で/inkを入力して読み込まれたか確認します。

詳細

@json-render/ink

JSON 仕様を、標準コンポーネント、データバインディング、表示条件、アクション、動的プロパティを備えたインタラクティブなターミナルコンポーネントツリーに変換する Ink ターミナルレンダラーです。

クイックスタート

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 条件には { "$and": [cond1, cond2] }、OR 条件には { "$or": [cond1, cond2] } を使用します。

動的なプロパティ式

任意のプロパティ値に、レンダリング時に解決されるデータ駆動型の式を指定できます。

  • { "$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(動的リスト)

コンテナー要素の 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." });

プロバイダー

プロバイダー用途
StateProviderコンポーネント間で状態を共有します(JSON Pointer パス)。制御モード用のオプションの store prop を受け取ります。
ActionProviderイベントシステムを介してディスパッチされたアクションを処理します
VisibilityProvider状態に基づく条件付きレンダリングを有効にします
ValidationProviderフォームフィールドの検証を行います
FocusProviderインタラクティブなコンポーネント間のフォーカスを管理します
JSONUIProviderすべてのコンテキストをまとめたプロバイダーです
外部ストア(制御モード)

StateProvider(または JSONUIProvider)に StateStore を渡すと、外部の状態管理を使用できます。

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カタログから型安全なコンポーネントレジストリを作成します
Rendererレジストリを使用して仕様をレンダリングします
createRenderer高レベル API:プロバイダーを組み込んだコンポーネントを作成します
JSONUIProviderすべてのコンテキストをまとめたプロバイダーです
schemaInk のフラット要素マップスキーマ(組み込みの状態アクションを含みます)
standardComponentDefinitionsすべての標準コンポーネントのカタログ定義
standardActionDefinitions標準アクションのカタログ定義
standardComponents事前に構築されたコンポーネント実装
useStateStore状態コンテキストにアクセスします
useStateValue状態から単一の値を取得します
useBoundProp$bindState/$bindItem 式の双方向バインディング
useActionsアクションコンテキストにアクセスします
useAction単一のアクションディスパッチ関数を取得します
useOptionalValidationuseValidation の例外をスローしないバリアント
useUIStreamAPI エンドポイントから仕様をストリーム配信します
createStateStoreフレームワークに依存しないインメモリ StateStore を作成します
StateStore外部の状態管理を組み込むためのインターフェイス
Components型付きコンポーネントマップ(カタログ対応)
Actions型付きアクションマップ(カタログ対応)
ComponentContext型付きコンポーネントコンテキスト(カタログ対応)
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 を使用します。
  • インラインの傾向表示には Sparkline、値の比較には BarChart を使用します。

コメント