Using kvx as a Library#

This guide covers everything you need to embed kvx in your own Go application — from a five-line quickstart to advanced customization.

Install#

go get github.com/oakwood-commons/kvx@latest

Quickstart#

There are two public packages. Pick the one that fits your use case:

PackageImportUse when you want…
pkg/coregithub.com/oakwood-commons/kvx/pkg/coreLoad data, evaluate expressions, render plain text tables — no terminal UI
pkg/tuigithub.com/oakwood-commons/kvx/pkg/tuiLaunch the full interactive TUI or render bordered tables

Minimal example — render a table (no TUI)#

package main

import (
    "fmt"

    "github.com/oakwood-commons/kvx/pkg/core"
    "github.com/oakwood-commons/kvx/pkg/tui"
)

func main() {
    data := map[string]any{
        "name":    "my-service",
        "version": "2.1.0",
        "healthy": true,
    }

    root, _ := core.LoadObject(data)

    // Bordered table, just like the kvx CLI
    fmt.Print(tui.RenderTable(root, tui.TableOptions{
        AppName:  "my-app",
        Path:     "_",
        Bordered: true,
    }))
}

Minimal example — launch the interactive TUI#

root, _ := core.LoadFile("data.yaml")

cfg := tui.DefaultConfig()
cfg.AppName = "my-app"

if err := tui.Run(root, cfg); err != nil {
    log.Fatal(err)
}

That’s it. Everything else below is optional.


Loading Data#

All loaders return a generic interface{} (map, slice, or scalar) that every other kvx function accepts. Format is auto-detected.

// From a file (JSON, YAML, NDJSON, JWT — auto-detected)
root, err := core.LoadFile("config.yaml")

// From a string
root, err := core.LoadRoot(`{"key": "value"}`)

// From bytes (useful for HTTP responses, embedded data, etc.)
root, err := core.LoadRootBytes(bodyBytes)

// From an existing Go value — map, slice, or struct
root, err := core.LoadObject(myStruct)

Custom structs#

LoadObject automatically converts structs to maps via JSON serialization so they work with the expression engine:

type Artifact struct {
    Name    string `json:"name"`
    Version string `json:"version"`
}

artifact := &Artifact{Name: "api", Version: "1.0.0"}
root, _ := core.LoadObject(artifact)
// root is now map[string]any{"name": "api", "version": "1.0.0"}

Slices of structs work the same way:

items := []any{
    &Artifact{Name: "api", Version: "1.0.0"},
    &Artifact{Name: "web", Version: "2.3.1"},
}
root, _ := core.LoadObject(items)

Evaluating Expressions#

The core.Engine provides CEL expression evaluation, navigation, and rendering:

engine, _ := core.New()

// Evaluate a CEL expression
result, err := engine.Evaluate("_.name", root)

// Navigate to a nested path
node, err := engine.NodeAtPath(root, "metadata.labels")

// Get table rows ([][]string) for your own renderer
rows := engine.Rows(root)

// Render a scalar value as a string
s := engine.Stringify(result)

Note: engine.RenderTable() renders a simple two-column KEY/VALUE table. For arrays of objects, use tui.RenderTable() instead — it auto-detects homogeneous arrays and renders them as columnar tables with field-name headers. See Rendering Tables below.

Common CEL expressions#

_.fieldName                          # access a field
_.items[0]                           # array index
_.items.filter(x, x.active)         # filter a list
_.items.map(x, x.name)              # map/transform
_.items.size()                       # list length
_.name.startsWith("api")            # string functions
_.count > 10 ? "high" : "low"       # ternary

Rendering Tables#

Bordered table (matches kvx CLI output)#

output := tui.RenderTable(root, tui.TableOptions{
    AppName:  "my-app",   // shown in the top border
    Path:     "_",        // shown in the bottom border
    Bordered: true,
    // Width: 0,          // 0 = auto-detect terminal width
    // NoColor: false,    // set true for CI/tests
})
fmt.Print(output)

Plain table (no borders)#

output := tui.RenderTable(root, tui.TableOptions{
    Bordered: false,
    NoColor:  true,
})

Array formatting#

Arrays of objects automatically render as multi-column tables. Control this with:

tui.RenderTable(root, tui.TableOptions{
    Bordered:     true,
    ColumnarMode: tui.ColumnarModeAuto,       // "auto" (default), "always", or "never"
    ArrayStyle:   tui.ArrayStyleNumbered,      // "numbered" (default), "index", "bullet", "none"
    ColumnOrder:  []string{"name", "version"}, // preferred column order
    HiddenColumns: []string{"sha"},            // columns to omit
})

Column display hints (schema)#

Control column headers, widths, alignment, and visibility using ColumnHints. You can build hints programmatically or parse them from a JSON Schema:

// Option 1: Build hints directly
hints := map[string]tui.ColumnHint{
    "user_id":   {DisplayName: "ID", MaxWidth: 10},
    "full_name": {DisplayName: "Name"},
    "score":     {Align: "right"},           // right-align numbers
    "internal":  {Hidden: true},             // hide this column
}

output := tui.RenderTable(root, tui.TableOptions{
    Bordered:    true,
    ColumnOrder: []string{"full_name", "user_id", "score"},
    ColumnHints: hints,
})
// Option 2: Parse from JSON Schema
schemaJSON := []byte(`{
    "type": "array",
    "items": {
        "type": "object",
        "required": ["id", "name"],
        "properties": {
            "id":    {"title": "ID", "type": "string", "maxLength": 10},
            "name":  {"title": "Name", "type": "string"},
            "price": {"type": "number"},
            "legacy": {"deprecated": true}
        }
    }
}`)

hints, err := tui.ParseSchema(schemaJSON)
if err != nil {
    log.Fatal(err)
}

output := tui.RenderTable(root, tui.TableOptions{
    Bordered:    true,
    ColumnHints: hints,
})

ParseSchema derives hints from standard JSON Schema fields:

JSON Schema FieldColumnHint Effect
titleDisplayName — column header text
maxLengthMaxWidth — cap column width
enumMaxWidth — longest enum value
format (date, uuid, etc.)MaxWidth — auto-calculated
type: integer/numberAlign: "right"
deprecated: trueHidden: true
required arrayPriority boost

Unified Render function#

If you want to let the caller choose the output format at runtime (table, list, YAML, JSON), use tui.Render with an OutputFormat:

// format could come from a flag, config, etc.
format := tui.FormatTable // or FormatList, FormatYAML, FormatJSON

output := tui.Render(root, format, tui.TableOptions{
    Bordered: true,
    NoColor:  true,
})
fmt.Print(output)

Available formats:

ConstantOutput
tui.FormatTableColumnar table (default)
tui.FormatListVertical property list
tui.FormatYAMLYAML
tui.FormatJSONIndented JSON

Interactive TUI#

Basic launch#

cfg := tui.DefaultConfig()
cfg.AppName = "my-app"

if err := tui.Run(root, cfg); err != nil {
    log.Fatal(err)
}

Configuration options#

tui.DefaultConfig() returns sensible defaults. Override only what you need:

cfg := tui.DefaultConfig()

// Appearance
cfg.AppName  = "my-app"     // header title
cfg.ThemeName = "warm"       // built-in themes: "dark", "warm", "cool"
cfg.NoColor   = true         // disable colors

// Behavior
cfg.InitialExpr = "_.items"  // start with an expression pre-evaluated

// UI text
cfg.KeyHeader        = "FIELD"           // table header (default: "KEY")
cfg.ValueHeader      = "DATA"            // table header (default: "VALUE")
cfg.InputPlaceholder = "Type a path…"    // input bar placeholder

// Feature toggles (pass *bool)
f := false
cfg.AllowFilter      = &f  // disable search/filter (F3)
cfg.AllowSuggestions = &f  // disable autocomplete

See tui.Config for the full list of fields.

Snapshot mode (non-interactive)#

Render exactly what the TUI would show, then exit — useful for CI or scripted output:

output := tui.RenderSnapshot(root, cfg)
fmt.Print(output)

Custom CEL Functions#

Add your own functions to the expression engine so they appear in TUI suggestions:

import (
    "github.com/google/cel-go/cel"
    "github.com/google/cel-go/common/types"
    "github.com/google/cel-go/common/types/ref"
    "github.com/oakwood-commons/kvx/pkg/tui"
)

// 1. Build a CEL environment with your functions.
env, _ := cel.NewEnv(
    cel.Variable("_", cel.DynType),
    cel.Function("double",
        cel.Overload("double_int",
            []*cel.Type{cel.IntType}, cel.IntType,
            cel.FunctionBinding(func(args ...ref.Val) ref.Val {
                return args[0].(types.Int) * 2
            }),
        ),
    ),
)

// 2. Create a provider (optional hints show in autocomplete).
provider := tui.NewCELExpressionProvider(env, map[string]string{
    "double": "e.g. double(5) → 10",
})

// 3. Plug it in before launching the TUI.
tui.SetExpressionProvider(provider)

cfg := tui.DefaultConfig()
tui.Run(root, cfg)

Working Examples#

ExampleDescriptionRun
examples/embed-tuiLoad a file, render a bordered table or launch the TUIgo run ./examples/embed-tui sample.json
examples/custom_structLoad Go structs, evaluate CEL expressionsgo run ./examples/custom_struct
examples/core-cliHeadless CLI — load, evaluate, render (no TUI)go run ./examples/core-cli data.yaml "_.items[0]"

API Quick Reference#

pkg/core#

Function / MethodDescription
core.LoadFile(path)Load JSON/YAML/NDJSON/JWT from disk
core.LoadRoot(input)Parse a string
core.LoadRootBytes(data)Parse bytes
core.LoadObject(value)Wrap a Go value (map, slice, struct)
core.New(opts...)Create an Engine with defaults
engine.Evaluate(expr, root)Run a CEL expression
engine.NodeAtPath(root, path)Navigate to a nested node
engine.Rows(node)Convert a node to [][]string rows
engine.RenderTable(node, noColor, keyW, valW)Render a plain KEY/VALUE table (no columnar detection — use tui.RenderTable for arrays)
engine.Stringify(node)Render a scalar as a display string

pkg/tui#

FunctionDescription
tui.Run(root, cfg, opts...)Launch the interactive TUI
tui.Render(node, format, opts)Render using an OutputFormat (FormatTable, FormatList, FormatTree, FormatMermaid, FormatYAML, FormatJSON)
tui.RenderTable(node, opts)Render a static table (bordered or plain; auto-detects columnar mode for arrays)
tui.RenderList(node, noColor)Render a vertical list (properties stacked per object, like -o list)
tui.RenderTree(node, opts)Render an ASCII tree structure (like -o tree)
tui.RenderMermaid(node, opts)Render a Mermaid flowchart diagram (like -o mermaid)
tui.RenderSnapshot(root, cfg)Render a full TUI frame as a string
tui.DefaultConfig()Get baseline TUI configuration
tui.DetectTerminalSize()Get terminal width and height
tui.NewCELExpressionProvider(env, hints)Create an expression provider from a CEL env
tui.SetExpressionProvider(p)Override the global expression provider
tui.ResetExpressionProvider()Restore the default provider