skillZs
★ LIVE SKILL TAGS ★
>>> LIVE SKILLS INDEX <<<
* OPEN SOURCE *
NO LOGIN, NO TRACKING
※ REAL INSTALL DATA ※
← back to all skills
yaklang/yak-skills105 installs

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-binding
view source ↗

Is 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. 相关路由

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 的两种模式:
    • 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)向已声明表格写一行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-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

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>