yakit-ui-binding について
yak プラグイン開発者向けのリファレンススキルです。yakit の出力が gRPC ストリームを通じて Yakit の UI に届く仕組みを、API と実行例で説明します。
主な機能
- 01スクリプトから UI へのメッセージ経路
- 02ログと進捗表示の API
- 03テーブル、グラフ、ステータスカード
- 04リスク、サイトツリー、テキストタブ
- 05コマンドラインでの出力動作
活用シーン
- 01yak プラグインの UI 出力選択
- 02動的テーブルや進捗更新のデバッグ
- 03Yakit コンポーネントへのメッセージ経路確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yaklang/ yak-skills --skill yakit-ui-bindingインストール後、Claude Code で/yakit-ui-bindingを入力して読み込まれたか確認します。
同梱ファイルexamples/ui-output-tour.yak
詳細
SKILL:yak スクリプトと Yakit UI のインタラクションの仕組み
AI LOAD INSTRUCTION:yak スクリプトは独立したコマンドラインプログラムではありません。Yakit エンジンで実行すると、yakit.* ライブラリ関数は gRPC ExecResult 双方向ストリームを介して構造化メッセージをフロントエンドに送信します。フロントエンドでは
useHoldGRPCStreamが message type / log level に基づいて振り分け、 さまざまな UI コンポーネント(ログパネル、プログレスバー、テーブル、グラフ、リスク一覧、Web サイトツリーなど)に表示します。このページでは、この一連の流れを解説し、各 UI 出力関数の API リファレンスと実行可能な例を紹介します。プラグインを作成するとき、「X を画面に表示する」方法を調べるには、ここを参照してください。
0. 関連ルート
- 総合入口:yak
- Yakit の基本的な使い方と機能エリアの説明:yakit-basic
- データベース操作(db ライブラリ):yaklang-database
- ネイティブプラグイン + cli 引数フォーム:yakit-native-plugin
- 右クリック codec プラグイン:yakit-rightclick-plugin
- 検証ツールチェーン:yaklang-toolchain
1. 中核となるメンタルモデル:yak スクリプトから UI までの全体の流れ
flowchart LR
subgraph Script["yak 脚本 (引擎内执行)"]
Call["yakit.Info(...)<br/>yakit.SetProgress(...)<br/>yakit.EnableTable(...)<br/>yakit.Output(table)"]
end
subgraph Bridge["gRPC 桥接 (引擎 -> 前端)"]
Client["YakitClient.send()<br/>封装为 ExecResult"]
Stream["gRPC 双向流<br/>ExecResult.IsMessage=true<br/>Message=JSON(YakitMessage)"]
end
subgraph Frontend["Yakit 前端"]
Hook["useHoldGRPCStream<br/>解析 YakitMessage"]
Route{"type / level<br/>路由"}
UI["日志面板 / 进度条<br/>表格 / 图表 / 卡片<br/>风险列表 / 网站树"]
end
Call --> Client --> Stream --> Hook --> Route --> UI
重要なポイント:
- yakit.* 関数は DOM を直接操作しません:
YakitLog/YakitProgress/YakitFeatureなどの構造体を作成し、YakitClient.send()を介して送信します。 - send には 2 つのモードがあります:
- VirtualYakitClient(コマンドラインの
yak xxx.yak):send コールバックはExecResultを stdout(log.Info)に出力します。 - gRPC YakitClient(Yakit エンジン環境):send は
ExecResultを gRPC 双方向ストリーム経由でフロントエンドに送信します。
- VirtualYakitClient(コマンドラインの
- フロントエンドは type + level に基づいて振り分けます:
useHoldGRPCStreamはYakitMessageJSON を解析し、type(log/progress/status-card)とlogData.level(json-feature/feature-table-data/json-risk/info/warn/...)に基づいて さまざまな UI コンポーネントに振り分けます。
ソースコードの場所:
- エンジン側のエクスポート:
yaklang/common/yak/yaklib/yakit.go(YakitExports/GetExtYakitLibByClient)- Client の実装:
yaklang/common/yak/yaklib/yakit_client.go(YakitClient.Output/YakitLog/send)- メッセージのシリアライズ:
yaklang/common/yak/yaklib/yakit.go(MarshalYakitOutput/YakitMessageGenerator)- フロントエンドでの受信:
yakit/app/renderer/src/main/src/hook/useHoldGRPCStream/useHoldGRPCStream.ts
2. メッセージプロトコル:YakitMessage の構造
エンジンからフロントエンドに送られる各メッセージは ExecResult{IsMessage: true, Message: JSON} です。ここで Message は
YakitMessage を JSON にシリアライズしたものです。
// yaklang/common/yak/yaklib/yakit.go
type YakitMessage struct {
Type string `json:"type"` // "log" / "progress" / "status-card"
Content json.RawMessage `json:"content"` // YakitLog / YakitProgress / YakitStatusCard
}
type YakitLog struct {
Level string `json:"level"` // info/warn/error/success/debug/code/markdown/text/report/file/
// json-feature/feature-table-data/feature-text-data/
// json-risk/json-table/json-graph/json-httpflow/fingerprint/...
Data string `json:"data"` // 内容或 JSON 字符串
Timestamp int64 `json:"timestamp"`
}
フロントエンドの useHoldGRPCStream による受信処理(簡略版):
// yakit/app/renderer/src/main/src/hook/useHoldGRPCStream/useHoldGRPCStream.ts
if (obj.type === 'progress') { /* 进度条 */ }
if (obj.type === 'log' && logData.level === 'feature-status-card-data') { /* 状态卡片 */ }
if (obj.type === 'log' && logData.level === 'json-feature') { /* 启用表格/网站树/文本标签页 */ }
if (obj.type === 'log' && logData.level === 'feature-table-data') { /* 表格行数据 */ }
if (obj.type === 'log' && logData.level === 'feature-text-data') { /* 文本标签页数据 */ }
if (obj.type === 'log' && logData.level === 'json-risk') { /* 风险列表 */ }
// 其余 log -> 日志面板
3. UI 出力関数の分類リファレンス
3.1 ログ出力(ログパネル)
| 関数 | UI 表示 | 説明 |
|---|---|---|
yakit.Info(format, args...) | info レベルのログ行 | printf 形式のフォーマットに対応 |
yakit.Warn(format, args...) | warn レベルのログ行(黄色) | 同上 |
yakit.Error(format, args...) | error レベルのログ行(赤色) | 同上 |
yakit.Success(msg) | success レベルのログ行(緑色) | printf は行わず、結合済みの文字列を受け取ります |
yakit.Debug(msg) | debug レベルのログ行 | 同上 |
yakit.Text(msg) | text ブロック(まとまったテキスト) | 複数行テキスト。フォーマットは行いません |
yakit.Code(msg) | code ブロック(等幅フォント) | コード/メッセージ |
yakit.Markdown(md) | Markdown レンダリング | 見出し/リスト/テーブル/太字に対応 |
yakit.Report(id) | レポート参照 | ID でレポートを参照 |
注意:
yakit.Info/yakit.Warn/yakit.Errorは printf 形式のフォーマットに対応しています(最初の引数はフォーマット文字列です)。yakit.Success/yakit.Text/yakit.Code/yakit.Markdownは printf 形式のフォーマットを行わないため、先にsprintf/f-string で文字列を結合してください。log.infoは printf 形式です。%を含む内容を出力する場合は、log.info("%s", x)のようにプレースホルダーを使い、文字列を直接結合しないでください。
3.2 プログレスバー
| 関数 | UI 表示 | 説明 |
|---|---|---|
yakit.SetProgress(f) | メインプログレスバー | f は 0.0~1.0 |
yakit.SetProgressEx(id, f) | 指定した id のプログレスバー | 複数の独立したプログレスバーを同時に管理できます |
3.3 ステータスカード(重要な指標を示す小型カード)
| 関数 | UI 表示 | 説明 |
|---|---|---|
yakit.StatusCard(id, data, tags...) | ステータスカード | 同じ id のカードはその場で更新されます。tags はグループ化に使用します |
3.4 静的テーブル(一括出力)
| 関数 | UI 表示 | 説明 |
|---|---|---|
yakit.NewTable(head...) | テーブルオブジェクトを作成 | head は列名です |
table.Append(row...) | 行を追加 | 各引数が各列に対応します |
yakit.Output(table) | テーブルをレンダリング | 共通の出力経路を使用します |
yakit.NewTableは「データを収集してからまとめて表示する」静的テーブルです。スキャンしながら結果を表示する場合は、EnableTable + TableDataを使用してください(3.5 を参照)。
3.5 動的テーブル(リアルタイムの増分更新)
| 関数 | UI 表示 | 説明 |
|---|---|---|
yakit.EnableTable(name, columns) | 動的テーブルを宣言 | columns は列名のリストです |
yakit.TableData(name, data) | 宣言済みのテーブルに 1 行書き込む | data は map で、キーは列名に対応します。"uuid" を含めて行の識別子を指定することもできます |
EnableTableはjson-feature(feature=fixed-table)を送信してテーブルを宣言し、TableDataはfeature-table-dataを送信して 1 行ずつ書き込みます。同じ uuid で再度書き込むと、同じ行を「更新」できます。
3.6 グラフ
| 関数 | UI 表示 | 説明 |
|---|---|---|
yakit.NewLineGraph(name...) | 折れ線グラフ | 推移の表示 |
yakit.NewBarGraph(name...) | 棒グラフ | カテゴリの比較 |
yakit.NewPieGraph(name...) | 円グラフ | 割合/内訳 |
yakit.NewWordCloud(name...) | ワードクラウド | キーワードの出現頻度 |
graph.Add(key, value) | データポイントを追加 | すべてのグラフで共通 |
yakit.Output(graph) | グラフをレンダリング | 共通の出力経路を使用します |
4 種類のグラフコンストラクターはシグネチャがまったく同じで、表示形式だけが異なります。グラフオブジェクトは
yakit.Outputで送信され、フロントエンドはjson-graphlevel に基づいてレンダリングします。
3.7 网站树
| 函数 | UI 展示 | 说明 |
|---|---|---|
yakit.EnableWebsiteTrees(targets) | 网站树标签页 | targets 为逗号分隔的目标 |
3.8 文本标签页
| 函数 | UI 展示 | 说明 |
|---|---|---|
yakit.EnableText(tabName) | 文本标签页 | 声明一个文本标签页 |
yakit.TextTabData(tabName, data) | 文本标签页数据 | 向已声明的标签页追加文本 |
3.9 DOT 图标签页
| 函数 | UI 展示 | 说明 |
|---|---|---|
yakit.EnableDotGraphTab(tabName) | DOT 图标签页 | 声明标签页 |
yakit.OutputDotGraph(tabName, dotData) | DOT 图数据 | 输出 Graphviz DOT 字符串 |
3.10 统一输出通道
| 函数 | 说明 |
|---|---|
yakit.Output(obj) | 自动按对象类型选择输出通道:Table/Graph/Risk/HTTPFlow/Fingerprint/StatusCard/TableData/... |
yakit.Output是"智能路由"——传入*YakitTable走json-table,传入*YakitGraph走json-graph, 传入*schema.Risk走json-risk,传入*fp.MatchResult走fingerprint,传入*YakitFixedTableData走feature-table-data,传入*YakitStatusCard走feature-status-card-data... 这意味着可以把扫描器的原生结果对象直接 Output 出去,由前端渲染成对应的卡片/表格。
3.11 风险与文件
| 函数 | UI 展示 | 说明 |
|---|---|---|
risk.NewRisk(url, risk.title(...), ...) | 风险列表 | 详见各 hotpatch skill 中的 risk 用法 |
yakit.NewHTTPFlowRisk(...) | HTTP 流量风险 | 带请求/响应的风险对象 |
yakit.File(path, desc...) | 文件卡片 | 文件信息/操作记录 |
yakit.FileReadAction(...) 等系列 | 文件操作记录 | 读/写/创建/删除/状态/权限/查找 |
3.12 流式输出
| 函数 | UI 展示 | 说明 |
|---|---|---|
yakit.Stream(streamType, streamId, reader) | 流式日志 | 逐字符读取 reader 并推送 |
4. 完整示例:一个输出多种 UI 组件的插件
// 关键词: yakit.Output, EnableTable, TableData, StatusCard, SetProgress, NewTable, NewBarGraph, Markdown
// 适用: yak 原生插件(在 Yakit 中执行时各 UI 组件实时展示)
func runPlugin() {
// 1. 进度条
yakit.SetProgress(0)
// 2. 状态卡片(关键指标)
yakit.StatusCard("Targets", "0/10", "progress")
// 3. 动态表格(边扫边出)
yakit.EnableTable("Port Scan Result", ["host", "port", "service"])
// 模拟扫描
results = [["10.0.0.1", "80", "http"], ["10.0.0.1", "443", "https"], ["10.0.0.2", "22", "ssh"]]
for i = 0; i < len(results); i++ {
r = results[i]
// 写动态表格
yakit.TableData("Port Scan Result", {"host": r[0], "port": r[1], "service": r[2]})
// 更新进度与卡片
yakit.SetProgress(float(i + 1) / float(len(results)))
yakit.StatusCard("Targets", sprintf("%d/%d", i + 1, len(results)), "progress")
yakit.Info("scanned %s:%s -> %s", r[0], r[1], r[2])
sleep(0.05)
}
yakit.SetProgress(1.0)
yakit.StatusCard("Targets", "10/10", "done")
// 4. 静态表格(汇总)
table = yakit.NewTable("Host", "Port", "Service")
for r in results { table.Append(r[0], r[1], r[2]) }
yakit.Output(table)
// 5. 柱状图
graph = yakit.NewBarGraph("port distribution")
graph.Add("80", 1)
graph.Add("443", 1)
graph.Add("22", 1)
yakit.Output(graph)
// 6. Markdown 报告
yakit.Markdown(sprintf("# Scan Report\n\n- hosts: 2\n- open ports: %d\n", len(results)))
yakit.Success("scan completed")
}
func runSelfTest() {
// 命令行自测: 调用纯函数验证逻辑,不依赖 UI
// yakit.* 在命令行环境走 VirtualYakitClient, 输出到 stdout
runPlugin()
assert true, "should complete without error"
}
if YAK_MAIN {
runSelfTest()
}
5. 命令行环境下的 yakit.* 行为(重要)
当脚本不在 Yakit 引擎中运行(即 yak xxx.yak 命令行运行)时:
AutoInitYakit()检测到没有--yakit-webhook参数 → 使用emptyVirtualClient(NewVirtualYakitClient)。emptyVirtualClient的 send 回调把ExecResult打印到log.Info(即 stdout)。- 因此
yakit.Info("hello")在命令行下等同于log.Info("hello"),yakit.Output(table)会打印 table 的 JSON。
这意味着:含 yakit.* 调用的脚本在命令行下不会崩溃,所有输出退化为日志打印。所以
"先在命令行自测逻辑,再粘回 Yakit 使用"的安全调试闭环依然成立。
源码:
yaklang/common/yak/yaklib/yakit.go中emptyVirtualClient和AutoInitYakit。
6. 引擎如何注入 yakit 全局变量
引擎启动脚本前,通过 SetEngineClient 把一个 YakitClient 实例注入为脚本的 yakit 全局变量:
// yaklang/common/yak/yaklib/yakit_client.go
func SetEngineClient(e *antlr4yak.Engine, client *YakitClient) {
e.OverrideRuntimeGlobalVariables(map[string]any{
"yakit": GetExtYakitLibByClient(client),
"risk": map[string]any{ /* risk 库, 内部用 client 做 risk 输出 */ },
})
InitYakit(client) // 设置全局默认客户端
}
GetExtYakitLibByClient(client) 返回一个 map[string]interface{},其中每个 key 就是脚本能调用的
yakit.xxx 函数名,value 是绑定到该 client 的函数。因此同一个脚本在不同 client 下运行:
- gRPC client → 输出到前端 UI
- VirtualYakitClient → 输出到 stdout
- 空 client → 输出被丢弃
7. 前端 useHoldGRPCStream 消费逻辑详解
前端 useHoldGRPCStream 是所有插件/扫描结果展示的核心 hook,它:
- 通过
yakitStream.onData(token, callback)监听 gRPC 流。 - 每收到一个
ExecResult,解析YakitMessageJSON。 - 按
type+level路由到不同缓冲区:
type | level | 前端缓冲区 | UI 组件 |
|---|---|---|---|
progress | - | progressKVPair (Map) | 进度条 |
log | feature-status-card-data | cardKVPair (Map) | 状态卡片 |
log | json-feature (feature=fixed-table) | tabTable (Map) | 动态表格 Tab |
log | json-feature (feature=website-trees) | tabWebsite | 网站树 Tab |
log | json-feature (feature=text) | tabsText (Map) | 文本 Tab |
log | feature-table-data | 更新 tabTable 中对应表格 | 动态表格行 |
log | feature-text-data | 更新 tabsText | 文本标签页内容 |
log | json-risk | riskMessages (Array) | 风险列表 |
log | info/warn/error/success/debug/text/code/markdown | messages (Array) | 日志面板 |
log | json-table | messages (Array) | 日志面板(静态表格 JSON) |
log | json-graph | messages (Array) | 日志面板(图表 JSON) |
- 定时(默认 500ms)把缓冲区快照到 React state,触发 UI 重渲染。
理解这条链路后,你就知道"写
yakit.EnableTable时前端在做什么"——它在等json-feature消息来 建表格 Tab,再等feature-table-data消息逐行填充。
8. 坑与注意事项
| 坑 | 错误做法 | 正确做法 |
|---|---|---|
yakit.Info 含 % 直接拼接 | yakit.Info("progress: 50%") | yakit.Info("progress: 50%%") 或 yakit.Info("%s", "progress: 50%") |
yakit.Success 当 printf 用 | yakit.Success("found %d", n) | yakit.Success(sprintf("found %d", n)) |
动态表格 TableData 没先 EnableTable | 直接 TableData | 先 EnableTable 声明,再 TableData 写行 |
| 表格 data 中含嵌套对象/数组 | yakit.TableData("t", {"info": {"a": 1}}) | 展平为基本类型,前端过滤掉含对象的行 |
| 状态卡片 id 不固定 | 每次随机 id | 用固定 id 实现原地更新 |
| 并发 hook 里调用 yakit.* | 多个 goroutine 同时 yakit.Info | yakit.* 本身并发安全(send 是线程安全的),但避免在 hook 里大量 yakit.Info 影响性能 |
9. 示例 (examples/)
| 文件 | 演示内容 | 验证 |
|---|---|---|
| examples/ui-output-tour.yak | 日志/进度条/状态卡片/静态表格/动态表格/柱状图/Markdown 全链路 | yak <file> 自测 |
10. 验证
cd /Users/v1ll4n/Projects/yaklang
go run common/yak/cmd/yak.go skills/yakit-ui-binding/examples/ui-output-tour.yak
命令行运行时,yakit.* 走 VirtualYakitClient,所有输出退化为 stdout 日志。
在 Yakit 中运行同一脚本,各 UI 组件实时展示。
合格标准:10 秒内完成、无 panic、log 全英文、末尾出现 ... self test passed。
参考来源
- 引擎侧 yakit 库导出:
yaklang/common/yak/yaklib/yakit.go(YakitExports/GetExtYakitLibByClient) - Client 实现:
yaklang/common/yak/yaklib/yakit_client.go(YakitClient.Output/YakitLog/send) - Viewer 扩展(EnableTable/StatusCard 等):
yaklang/common/yak/yaklib/yakit_viewer.go - 消息序列化:
yaklang/common/yak/yaklib/yakit.go(MarshalYakitOutput/YakitMessageGenerator) - 脚本执行:
yaklang/common/yak/yakscript/exec.go(ExecScriptWithParam/ExecScriptWithExecParam) - 前端 gRPC 流消费:
yakit/app/renderer/src/main/src/hook/useHoldGRPCStream/useHoldGRPCStream.ts - 前端类型定义:
yakit/app/renderer/src/main/src/hook/useHoldGRPCStream/useHoldGRPCStreamType.d.ts - 前端结果展示:
yakit/app/renderer/src/main/src/pages/plugins/operator/pluginExecuteResult/PluginExecuteResult.tsx