SuperAPI AI Directory

yakit-ui-binding

Coding & development

View repository

Updated 10-16 02:12

About this skill

SKILL: Interaction mechanism between yak scripts and the Yakit UI

AI LOAD INSTRUCTION: yak scripts are not isolated command-line programs — when running in the Yakit engine, yakit.* library functions push structured messages to the frontend through the gRPC ExecResult bidirectional stream, and the frontend useHoldGRPCStream routes them by message type / log level to different UI components (log panel, progress bar, table, chart, risk list, website tree...). This page explains this complete链路 in full and provides an API quick reference and runnable examples for each UI output function. When writing a plugin and wanting "the interface to display X", look it up here.

0. Related routes

1. Core mental model: the complete chain from yak script to 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

Key understanding:

  1. yakit.* functions do not directly manipulate the DOM: they construct YakitLog / YakitProgress / YakitFeature and other structs, and emit them through YakitClient.send() .
  2. Two modes of send:
    • VirtualYakitClient(command line yak xxx.yak): the send callback prints ExecResult to stdout (log.Info)。
    • gRPC YakitClient(Yakit engine environment): send pushes ExecResult to the frontend through the gRPC bidirectional stream.
  3. The frontend routes by type + level:useHoldGRPCStream parses YakitMessage JSON, and by type(log/progress/ status-card) and logData.level(json-feature/feature-table-data/json-risk/info/warn/...) dispatches to different UI components.

Source location:

  • Engine-side export:yaklang/common/yak/yaklib/yakit.go(YakitExports / GetExtYakitLibByClient)
  • Client implementation:yaklang/common/yak/yaklib/yakit_client.go(YakitClient.Output / YakitLog / send)
  • Message serialization:yaklang/common/yak/yaklib/yakit.go(MarshalYakitOutput / YakitMessageGenerator)
  • Frontend consumption:yakit/app/renderer/src/main/src/hook/useHoldGRPCStream/useHoldGRPCStream.ts

2. Message protocol: YakitMessage structure

Every message from the engine to the frontend is ExecResult{IsMessage: true, Message: JSON}, where Message is the JSON serialization of YakitMessage :

// 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"`
}

Frontend useHoldGRPCStream consumption logic (simplified):

// 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 output function category quick reference

3.1 Log output (log panel)

Function UI display Description
yakit.Info(format, args...) info-level log line Supports printf formatting
yakit.Warn(format, args...) warn-level log line (yellow) Same as above
yakit.Error(format, args...) error-level log line (red) Same as above
yakit.Success(msg) success-level log line (green) Does not do printf; accepts an already-assembled string
yakit.Debug(msg) debug-level log line Same as above
yakit.Text(msg) text block (entire block of text) Multiline text, no formatting
yakit.Code(msg) code block (monospace font) Code/packet
yakit.Markdown(md) Markdown rendering Supports headings/lists/tables/bold
yakit.Report(id) Report reference References a report by ID

Note:yakit.Info / yakit.Warn / yakit.Error supports printf formatting (the first argument is the format string); yakit.Success / yakit.Text / yakit.Code / yakit.Markdown does not do printf; you need to first use sprintf / f-string concatenation. log.info is printf-style; to print content containing % you need to use the log.info("%s", x) placeholder and cannot directly concatenate strings.

3.2 Progress bar

Function UI display Description
yakit.SetProgress(f) Main progress bar f is 0.0~1.0
yakit.SetProgressEx(id, f) Progress bar with the specified id Multiple independent progress bars can be maintained at the same time

3.3 Status card (small card for key metrics)

Function UI display Description
yakit.StatusCard(id, data, tags...) Status card The same id updates in place; tags are used for grouping

3.4 Static table (one-time output)

Function UI display Description
yakit.NewTable(head...) Create table object head is the column name
table.Append(row...) Append row Each parameter corresponds to one column
yakit.Output(table) Render table Unified output channel

yakit.NewTable is a static table that "collects everything before displaying it all at once". To output results while scanning, use EnableTable + TableData(see 3.5).

3.5 Dynamic table (real-time incremental updates)

Function UI display Description
yakit.EnableTable(name, columns) Declare a dynamic table columns is the list of column names
yakit.TableData(name, data) Write one row to the declared table data is a map, keys correspond to column names; may include "uuid" to control row identity

EnableTable sends json-feature(feature=fixed-table) to declare the table,TableData sends feature-table-data written row by row. Writing again with the same uuid will "update" the same row.

3.6 Charts

Function UI display Description
yakit.NewLineGraph(name...) Line chart Trend display
yakit.NewBarGraph(name...) Bar chart Categorical comparison
yakit.NewPieGraph(name...) Pie chart Proportion/composition
yakit.NewWordCloud(name...) Word cloud Keyword frequency
graph.Add(key, value) Add data point Common to all charts
yakit.Output(graph) Render chart Unified output channel

The four chart constructors have exactly the same signature and differ only in display form. The chart object is sent via yakit.Output and the frontend renders by json-graph level.

3.7 Website tree

Function UI display Description
yakit.EnableWebsiteTrees(targets) Website tree tab targets is a comma-separated list of targets

3.8 Text tab

Function UI display Description
yakit.EnableText(tabName) Text tab Declare a text tab
yakit.TextTabData(tabName, data) Text tab data Append text to the declared tab

3.9 DOT graph tab

Function UI display Description
yakit.EnableDotGraphTab(tabName) DOT graph tab Declare tab
yakit.OutputDotGraph(tabName, dotData) DOT graph data Output a Graphviz DOT string

3.10 Unified output channel

Function Description
yakit.Output(obj) Automatically select the output channel based on the object type: Table/Graph/Risk/HTTPFlow/Fingerprint/StatusCard/TableData/...

yakit.Output is "intelligent routing" — pass in *YakitTable to go to json-table, pass in *YakitGraph to go to json-graph, pass in *schema.Risk to go to json-risk, pass in *fp.MatchResult to go to fingerprint, pass in *YakitFixedTableData to go to feature-table-data, pass in *YakitStatusCard to go to feature-status-card-data... This means the scanner's native result objects can be Output directly, and the frontend renders them into the corresponding cards/tables.

3.11 Risks and Files

Function UI Display Description
risk.NewRisk(url, risk.title(...), ...) Risk List See the risk usage in each hotpatch skill for details
yakit.NewHTTPFlowRisk(...) HTTP Traffic Risk Risk object with request/response
yakit.File(path, desc...) File Card File information/operation records
yakit.FileReadAction(...) and other series such as yakit.FileReadAction(...) File operation records Read/Write/Create/Delete/Status/Permission/Find

3.12 Streaming Output

Function UI Display Description
yakit.Stream(streamType, streamId, reader) Streaming Log Read the reader character by character and push

4. Complete Example: A Plugin That Outputs Multiple UI Components

// 关键词: 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. Behavior of yakit.* in the Command-Line Environment (Important)

When the script does not run in the Yakit engine (i.e., yak xxx.yak when run from the command line):

  • AutoInitYakit() Detected that there is no --yakit-webhook parameter → use emptyVirtualClient(NewVirtualYakitClient)。
  • emptyVirtualClient 's send callback to print ExecResult to log.Info(i.e., stdout).
  • Therefore yakit.Info("hello") is equivalent to log.Info("hello"),yakit.Output(table) will print the table's JSON.

This means: scripts containing yakit.* calls will not crash on the command line, and all output degrades to log printing. So the safe debugging loop of "first self-test the logic on the command line, then paste it back into Yakit" still holds.

Source code:yaklang/common/yak/yaklib/yakit.go in emptyVirtualClient and AutoInitYakit。

6. How the Engine Injects the yakit Global Variable

Before the engine starts the script, it uses SetEngineClient to inject a YakitClient instance as the script's yakit global variable:

// 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) returns a map[string]interface{}, where each key is a yakit.xxx function name that the script can call, and the value is the function bound to that client. Therefore the same script running under different clients:

  • gRPC client → output to the frontend UI
  • VirtualYakitClient → output to stdout
  • empty client → output is discarded

7. Detailed Explanation of the Frontend useHoldGRPCStream Consumption Logic

The frontend useHoldGRPCStream is the core hook for displaying all plugin/scan results; it:

  1. Listens to the gRPC stream via yakitStream.onData(token, callback) .
  2. Each time a ExecResultis received, parses YakitMessage JSON。
  3. and routes by type + level to different buffers:
type level Frontend Buffer UI Component
progress - progressKVPair (Map) Progress Bar
log feature-status-card-data cardKVPair (Map) Status Card
log json-feature (feature=fixed-table) tabTable (Map) Dynamic Table Tab
log json-feature (feature=website-trees) tabWebsite Website Tree Tab
log json-feature (feature=text) tabsText (Map) Text Tab
log feature-table-data Update tabTable the corresponding table in Dynamic table row
log feature-text-data Update tabsText Text tab content
log json-risk riskMessages (Array) Risk list
log info/warn/error/success/debug/text/code/markdown messages (Array) Log panel
log json-table messages (Array) Log panel (static table JSON)
log json-graph messages (Array) Log panel (chart JSON)
  1. Periodically (default 500ms) snapshot the buffer to React state to trigger a UI re-render.

After understanding this chain, you know "what the frontend is doing when writing yakit.EnableTable "—it is waiting for json-feature messages to create the table Tab, then waits for feature-table-data messages to fill row by row.

8. Pitfalls and Notes

Pitfall Wrong approach Correct approach
yakit.Info contains % direct concatenation yakit.Info("progress: 50%") yakit.Info("progress: 50%%") or yakit.Info("%s", "progress: 50%")
yakit.Success used as printf yakit.Success("found %d", n) yakit.Success(sprintf("found %d", n))
Dynamic table TableData without first EnableTable directly TableData first EnableTable declaration, then TableData write rows
Table data contains nested objects/arrays yakit.TableData("t", {"info": {"a": 1}}) Flatten to primitive types, frontend filters out rows containing objects
Status card id is not fixed Random id each time Use a fixed id for in-place updates
Calling yakit.* in concurrent hooks Multiple goroutines calling yakit.Info simultaneously yakit.* itself is concurrency-safe (send is thread-safe), but avoid heavy yakit.Info in hooks to prevent performance impact

9. Examples (examples/)

File Demo content Validation
examples/ui-output-tour.yak Log/progress bar/status card/static table/dynamic table/bar chart/Markdown full chain yak <file> self-test

10. Validation

cd /Users/v1ll4n/Projects/yaklang
go run common/yak/cmd/yak.go skills/yakit-ui-binding/examples/ui-output-tour.yak

When running on the command line,yakit.* goes through VirtualYakitClient, and all output degrades to stdout logs. Running the same script in Yakit, each UI component displays in real time.

Pass criteria: completes within 10 seconds, no panic, log all in English, and ... self test passed。

Reference sources

  • Engine-side yakit library export:yaklang/common/yak/yaklib/yakit.go(YakitExports / GetExtYakitLibByClient)
  • Client implementation:yaklang/common/yak/yaklib/yakit_client.go(YakitClient.Output / YakitLog / send)
  • Viewer extensions (EnableTable/StatusCard etc.):yaklang/common/yak/yaklib/yakit_viewer.go
  • Message serialization:yaklang/common/yak/yaklib/yakit.go(MarshalYakitOutput / YakitMessageGenerator)
  • Script execution:yaklang/common/yak/yakscript/exec.go(ExecScriptWithParam / ExecScriptWithExecParam)
  • Frontend gRPC stream consumption:yakit/app/renderer/src/main/src/hook/useHoldGRPCStream/useHoldGRPCStream.ts
  • Frontend type definitions:yakit/app/renderer/src/main/src/hook/useHoldGRPCStream/useHoldGRPCStreamType.d.ts
  • Frontend result display:yakit/app/renderer/src/main/src/pages/plugins/operator/pluginExecuteResult/PluginExecuteResult.tsx