cli/docs/pkg/cli/output.md
Virgil 419e7f745b
All checks were successful
Security Scan / security (push) Successful in 19s
feat(cli): add formatted security log helper
2026-04-02 04:47:42 +00:00

7.8 KiB
Raw Blame History

title description
Output Functions Styled output, tables, trees, task trackers, glyphs, and formatting utilities.

Output Functions

All output functions support glyph shortcodes (:check:, :cross:, :warn:, :info:) which are auto-converted to Unicode symbols.

Styled Messages

cli.Success("All tests passed")     // ✓ All tests passed (green, bold)
cli.Successf("Built %d files", n)   // ✓ Built 5 files (green, bold)
cli.Error("Connection refused")     // ✗ Connection refused (red, bold, stderr)
cli.Errorf("Port %d in use", port)  // ✗ Port 8080 in use (red, bold, stderr)
cli.Warn("Deprecated flag used")    // ⚠ Deprecated flag used (amber, bold, stderr)
cli.Warnf("Skipping %s", name)     // ⚠ Skipping foo (amber, bold, stderr)
cli.Info("Using default config")    //  Using default config (blue)
cli.Infof("Found %d items", n)     //  Found 42 items (blue)
cli.Dim("Optional detail")         // Optional detail (grey, dimmed)

Error and Warn write to stderr and also log the message. Success and Info write to stdout.

Plain Output

cli.Println("Hello %s", name)      // fmt.Sprintf + glyph conversion + newline
cli.Print("Loading...")             // No newline
cli.Text("raw", "text")            // Like fmt.Println but with glyphs
cli.Blank()                         // Empty line
cli.Echo("key.label", args...)     // i18n.T translation + newline

Structured Output

cli.Label("version", "1.2.0")     // Version: 1.2.0 (styled key)
cli.Task("php", "Running tests")  // [php] Running tests
cli.Section("audit")               // ── AUDIT ──
cli.Hint("fix", "go mod tidy")    //   fix: go mod tidy
cli.Result(passed, "Tests passed") // ✓ or ✗ based on bool

Error Wrapping for Output

These display errors without returning them -- useful when you need to show an error but continue:

cli.ErrorWrap(err, "load config")            // ✗ load config: <error>
cli.ErrorWrapVerb(err, "load", "config")     // ✗ Failed to load config: <error>
cli.ErrorWrapAction(err, "connect")          // ✗ Failed to connect: <error>

All three are nil-safe -- they do nothing if err is nil.

Progress Indicator

Overwrites the current terminal line to show progress:

for i, item := range items {
    cli.Progress("check", i+1, len(items), item.Name) // Overwrites line
}
cli.ProgressDone() // Clears progress line

The verb is passed through i18n.Progress() for gerund form ("Checking...").

Severity Levels

cli.Severity("critical", "SQL injection found")  // [critical] red, bold
cli.Severity("high", "XSS vulnerability")        // [high] orange, bold
cli.Severity("medium", "Missing CSRF token")     // [medium] amber
cli.Severity("low", "Debug mode enabled")         // [low] grey

Check Results

Fluent API for building pass/fail/skip/warn result lines:

cli.Check("audit").Pass().Print()                  //   ✓ audit passed
cli.Check("fmt").Fail().Duration("2.3s").Print()   //   ✗ fmt    failed    2.3s
cli.Check("test").Skip().Print()                   //   - test skipped
cli.Check("lint").Warn().Message("3 warnings").Print()

Tables

Aligned tabular output with optional box-drawing borders:

t := cli.NewTable("REPO", "STATUS", "BRANCH")
t.AddRow("core-php", "clean", "main")
t.AddRow("core-tenant", "dirty", "feature/x")
t.Render()

Output:

REPO          STATUS  BRANCH
core-php      clean   main
core-tenant   dirty   feature/x

Bordered Tables

t := cli.NewTable("REPO", "STATUS").
    WithBorders(cli.BorderRounded)
t.AddRow("core-php", "clean")
t.Render()

Output:

╭──────────┬────────╮
│ REPO     │ STATUS │
├──────────┼────────┤
│ core-php │ clean  │
╰──────────┴────────╯

Border styles: BorderNone (default), BorderNormal, BorderRounded, BorderHeavy, BorderDouble.

Per-Column Cell Styling

t := cli.NewTable("REPO", "STATUS", "BRANCH").
    WithCellStyle(1, func(val string) *cli.AnsiStyle {
        if val == "clean" {
            return cli.SuccessStyle
        }
        return cli.WarningStyle
    }).
    WithMaxWidth(80)

Trees

Hierarchical output with box-drawing connectors:

tree := cli.NewTree("core-php")
tree.Add("core-tenant").Add("core-bio")
tree.Add("core-admin")
tree.Add("core-api")
tree.Render()

Output:

core-php
├── core-tenant
│   └── core-bio
├── core-admin
└── core-api

Styled nodes:

tree.AddStyled("core-tenant", cli.RepoStyle)

Task Tracker

Displays multiple concurrent tasks with live spinners. Uses ANSI cursor manipulation when connected to a TTY; falls back to line-by-line output otherwise.

tracker := cli.NewTaskTracker()
for _, repo := range repos {
    t := tracker.Add(repo.Name)
    go func(t *cli.TrackedTask) {
        t.Update("pulling...")
        if err := pull(repo); err != nil {
            t.Fail(err.Error())
            return
        }
        t.Done("up to date")
    }(t)
}
tracker.Wait()
cli.Println(tracker.Summary()) // "5/5 passed"

TrackedTask methods (Update, Done, Fail) are safe for concurrent use.

String Builders (No Print)

These return styled strings without printing, for use in composing output:

cli.SuccessStr("done")    // Returns "✓ done" styled green
cli.ErrorStr("failed")    // Returns "✗ failed" styled red
cli.WarnStr("warning")    // Returns "⚠ warning" styled amber
cli.InfoStr("note")       // Returns " note" styled blue
cli.DimStr("detail")      // Returns dimmed text
cli.Styled(style, "text") // Apply any AnsiStyle
cli.Styledf(style, "formatted %s", arg)

Glyph Shortcodes

All output functions auto-convert shortcodes to symbols:

Shortcode Unicode Emoji ASCII
:check: [OK]
:cross: [FAIL]
:warn: ⚠️ [WARN]
:info: [INFO]
:arrow_right: ➡️ ->
:bullet: *
:dash: -
:pipe: |
:corner: `
:tee: +
:pending: ...

Switch themes:

cli.UseUnicode() // Default
cli.UseEmoji()   // Emoji symbols
cli.UseASCII()   // ASCII fallback (also disables colours)

ANSI Styles

Build custom styles with the fluent AnsiStyle API:

style := cli.NewStyle().Bold().Foreground(cli.ColourBlue500)
fmt.Println(style.Render("Important text"))

Available methods: Bold(), Dim(), Italic(), Underline(), Foreground(hex), Background(hex).

The framework provides pre-defined styles using the Tailwind colour palette:

Style Description
SuccessStyle Green, bold
ErrorStyle Red, bold
WarningStyle Amber, bold
InfoStyle Blue
SecurityStyle Purple, bold
DimStyle Grey, dimmed
HeaderStyle Grey-200, bold
AccentStyle Cyan
LinkStyle Blue, underlined
CodeStyle Grey-300
NumberStyle Blue-300
RepoStyle Blue, bold

Colours respect NO_COLOR and TERM=dumb. Use cli.ColorEnabled() to check and cli.SetColorEnabled(false) to disable programmatically.

Formatting Utilities

cli.Truncate("long string", 10)  // "long st..."
cli.Pad("short", 20)             // "short               "
cli.FormatAge(time.Now().Add(-2*time.Hour))  // "2h ago"

Logging

The framework provides package-level log functions that delegate to the Core log service when available:

cli.LogDebug("cache miss", "key", "foo")
cli.LogInfo("server started", "port", 8080)
cli.LogWarn("slow query", "duration", "3.2s")
cli.LogError("connection failed", "err", err)
cli.LogSecurity("login attempt", "user", "admin")
cli.LogSecurityf("login attempt from %s", username)