mcpField
Y
yakit-ui-binding

yakit-ui-binding

github.com/yaklang開発10105 回インストール15 週間前に更新
リポジトリ

yakit-ui-binding について

yak プラグイン開発者向けのリファレンススキルです。yakit の出力が gRPC ストリームを通じて Yakit の UI に届く仕組みを、API と実行例で説明します。

主な機能

  1. 01スクリプトから UI へのメッセージ経路
  2. 02ログと進捗表示の API
  3. 03テーブル、グラフ、ステータスカード
  4. 04リスク、サイトツリー、テキストタブ
  5. 05コマンドラインでの出力動作

活用シーン

  1. 01yak プラグインの UI 出力選択
  2. 02動的テーブルや進捗更新のデバッグ
  3. 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. 関連ルート

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

重要なポイント:

  1. yakit.* 関数は DOM を直接操作しません:YakitLog/YakitProgress/YakitFeature などの構造体を作成し、 YakitClient.send() を介して送信します。
  2. send には 2 つのモードがあります:
    • VirtualYakitClient(コマンドラインの yak xxx.yak):send コールバックは ExecResult を stdout(log.Info)に出力します。
    • gRPC YakitClient(Yakit エンジン環境):send は ExecResult を gRPC 双方向ストリーム経由でフロントエンドに送信します。
  3. フロントエンドは type + level に基づいて振り分けます:useHoldGRPCStream は YakitMessage JSON を解析し、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-graph level に基づいてレンダリングします。

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,它:

  1. 通过 yakitStream.onData(token, callback) 监听 gRPC 流。
  2. 每收到一个 ExecResult,解析 YakitMessage JSON。
  3. 按 type + level 路由到不同缓冲区:
typelevel前端缓冲区UI 组件
progress-progressKVPair (Map)进度条
logfeature-status-card-datacardKVPair (Map)状态卡片
logjson-feature (feature=fixed-table)tabTable (Map)动态表格 Tab
logjson-feature (feature=website-trees)tabWebsite网站树 Tab
logjson-feature (feature=text)tabsText (Map)文本 Tab
logfeature-table-data更新 tabTable 中对应表格动态表格行
logfeature-text-data更新 tabsText文本标签页内容
logjson-riskriskMessages (Array)风险列表
loginfo/warn/error/success/debug/text/code/markdownmessages (Array)日志面板
logjson-tablemessages (Array)日志面板(静态表格 JSON)
logjson-graphmessages (Array)日志面板(图表 JSON)
  1. 定时(默认 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.Infoyakit.* 本身并发安全(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

コメント