yakit-ui-binding
yak 脚本与 Yakit UI 交互机制专题。讲清 yakit.* 库函数如何通过 gRPC ExecResult 流驱动界面渲染: yakit.Info/Warn/Error/Success 日志输出、yakit.SetProgress 进度条、yakit.NewTable/EnableTable/TableData 表格(静态 vs 动态)、yakit.NewLineGraph/NewBarGraph/NewPieGraph/NewWordCloud 图表、yakit.StatusCard 状态卡片、yakit.EnableWebsiteTrees 网站树、yakit.EnableText/TextTabData 文本标签页、yakit.Output 统一输出通道、以及从前端 useHoldGRPCStream 消费 ExecResult 到渲染 UI 的完整链路。当用户问"脚本怎么让 Yakit 显示表格/图表/进度条/卡片"或"yakit.Info 输出到哪里了"时使用。
How do I install this agent skill?
npx skills add https://github.com/yaklang/yak-skills --skill yakit-ui-bindingIs this agent skill safe to install?
- Gen Agent Trust Hubpass
This skill provides technical documentation and code examples for using the yakit.* UI library within the Yakit engine. It is entirely instructional, demonstrating how to display logs, tables, and charts through the engine's internal gRPC bridge. No security risks were identified.
- Socketpass
No alerts
- Snykpass
Risk: LOW · No issues
What does this agent skill do?
SKILL: yak 脚本与 Yakit UI 交互机制
AI LOAD INSTRUCTION: yak 脚本不是孤立的命令行程序——在 Yakit 引擎中运行时,yakit.* 库函数会通过 gRPC ExecResult 双向流把结构化消息推送到前端,前端
useHoldGRPCStream按 message type / log level 路由到 不同的 UI 组件(日志面板、进度条、表格、图表、风险列表、网站树...)。本页讲清这条完整链路,并给出每个 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 的两种模式:
- 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) | 向已声明表格写一行 | data 是 map,键对应列名;可含 "uuid" 控制行标识 |
EnableTable发送json-feature(feature=fixed-table) 声明表格,TableData发送feature-table-data逐行写入。用相同 uuid 再次写入可"更新"同一行。
3.6 图表
| 函数 | UI 展示 | 说明 |
|---|---|---|
yakit.NewLineGraph(name...) | 折线图 | 趋势展示 |
yakit.NewBarGraph(name...) | 柱状图 | 分类对比 |
yakit.NewPieGraph(name...) | 饼图 | 占比/构成 |
yakit.NewWordCloud(name...) | 词云 | 关键词频率 |
graph.Add(key, value) | 添加数据点 | 所有图表通用 |
yakit.Output(graph) | 渲染图表 | 统一输出通道 |
四种图表构造器签名完全一致,仅展示形态不同。图表对象通过
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
How can the creator link this skill?
Add the canonical catalog link to the repository README so users can inspect current installs and available audits. The publishing guide covers the complete discovery path.
<a href="https://skillzs.dev/skills/yaklang/yak-skills/yakit-ui-binding">View yakit-ui-binding on skillZs</a>