output

package
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 28, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package output renders command results in either pretty TTY form or stable machine-readable JSON. The renderer is created once per command in PersistentPreRunE and passed via the Runtime.

Contract:

  • stdout is data.
  • stderr is chatter (Success / Info / Hint messages, spinners, errors).
  • --json never auto-activates from pipe detection; the user opts in.
  • --no-color (or NO_COLOR env) disables ALL ANSI output, including text-style attributes like bold (not just color).

Index

Constants

This section is empty.

Variables

View Source
var (
	// BrandRed is RevenueCat red — identity moments only (titles, accents),
	// never errors, so red text still means something is wrong.
	BrandRed = lipgloss.AdaptiveColor{Light: "#D40017", Dark: "#F2545B"}
	// ErrorRed is deeper than brand red so failures read as failures.
	ErrorRed  = lipgloss.AdaptiveColor{Light: "#B70004", Dark: "#F03F3C"}
	GreenOK   = lipgloss.AdaptiveColor{Light: "#00845E", Dark: "#11D483"}
	WarnAmber = lipgloss.AdaptiveColor{Light: "#AC592D", Dark: "#E79462"}
	InfoBlue  = lipgloss.AdaptiveColor{Light: "#4F62C4", Dark: "#7D8EF2"}
	// AccentViolet is the interaction accent (focus, selection, cursors) —
	// RC violet. Red is reserved: brand red for section/title landmarks
	// only, error red for failures only, because in terminals colored red
	// text reads as danger regardless of brand.
	AccentViolet = lipgloss.AdaptiveColor{Light: "#7C5CA8", Dark: "#A987D1"}
	// NeutralGray is chrome: borders, archived states, secondary chips.
	NeutralGray = lipgloss.AdaptiveColor{Light: "#8A8A8A", Dark: "#585858"}
)

RevenueCat brand palette, taken from the dashboard's design tokens (revenuecat-app src/styles/theme.css + palette-primitives.css). Each color carries a light- and dark-background variant; lipgloss picks per terminal and degrades gracefully on 256-color terminals.

View Source
var (
	StyleTitle   = lipgloss.NewStyle().Bold(true)
	StyleAccent  = lipgloss.NewStyle().Foreground(BrandRed).Bold(true)
	StyleSuccess = lipgloss.NewStyle().Foreground(GreenOK).Bold(true)
	StyleWarn    = lipgloss.NewStyle().Foreground(WarnAmber)
	StyleError   = lipgloss.NewStyle().Foreground(ErrorRed).Bold(true)
	StyleInfo    = lipgloss.NewStyle().Foreground(InfoBlue)
	StyleDim     = lipgloss.NewStyle().Faint(true)
	// StyleCommand is the interaction accent for commands the user can type.
	StyleCommand = lipgloss.NewStyle().Foreground(AccentViolet)
)

Shared semantic styles. Commands and TUIs should use these instead of hand-picking ANSI colors so the whole CLI reads as one product.

View Source
var ErrBadFormat = errors.New("invalid --format expression")

ErrBadFormat signals an unparseable --format expression. CLI maps this to exit 2 (usage error) so agents can distinguish bad input from runtime failures.

Functions

func EscapeC1JSON added in v0.1.4

func EscapeC1JSON(b []byte) []byte

EscapeC1JSON rewrites UTF-8-encoded C1 codepoints (0xC2 0x80–0x9F; in valid UTF-8, 0xC2 only ever appears as that lead byte) as JSON \u escapes: encoding/json escapes C0 on the wire but leaves C1 as raw bytes.

func HelpCommand

func HelpCommand(color bool, s string) string

func HelpDim

func HelpDim(color bool, s string) string

func HelpHeader

func HelpHeader(color bool, s string) string
func Hyperlink(styledLabel, url string) string

Hyperlink wraps styledLabel in an OSC 8 terminal hyperlink pointing at url. Supporting terminals make it clickable; others render the label text. This is the one place the OSC 8 escape lives.

func Sanitize added in v0.1.4

func Sanitize(s string) string

Sanitize strips control characters (C0 except \n and \t, DEL, and C1) from a value before it is rendered as human output. API values are arbitrary text and must render as visible characters only — they must never be able to move the cursor or address the terminal.

func SanitizeLine added in v0.1.4

func SanitizeLine(s string) string

SanitizeLine is Sanitize for single-line contexts — table cells, labels, breadcrumbs, chips — where a newline would fake extra rows and a tab would shift columns; both collapse to a space.

Types

type Card

type Card struct {
	// Title is the bold one-liner at the top (e.g. "cus_abc · iOS · US").
	Title string
	// Subtitle is a dimmer second line (e.g. "first seen 2024-12-18 · last seen 2025-05-15").
	Subtitle string
	// Sections render in order. Use Chips for entitlements, Table for
	// subscriptions/purchases, Lines for arbitrary k:v pairs.
	Sections []CardSection
	// Raw is the structured payload returned under --json.
	Raw any
}

Card is a structured detail view used by commands like `rc customers show`. It's an alternative to RenderTable for "one entity, many facets" output. JSON mode bypasses Card entirely and emits Raw via Render().

Sections render in the order they're added. Empty sections are skipped so the output adapts when subresources are missing.

type CardLine

type CardLine struct {
	Key   string
	Value string
}

type CardSection

type CardSection struct {
	Heading string
	Chips   []Chip     // colored badges for short labels
	Table   *CardTable // for tabular data inside the card
	Lines   []CardLine // for k: v pairs
	Empty   string     // shown (dimmed) when the section has no content
}

type CardTable

type CardTable struct {
	Columns []string
	Rows    [][]string
}

type Chip

type Chip struct {
	Label string
	Tone  ChipTone
}

type ChipTone

type ChipTone int
const (
	ToneNeutral ChipTone = iota
	ToneActive
	ToneArchived
	ToneExpired
	ToneTrial
	ToneWarning
)

type Renderer

type Renderer struct {
	// contains filtered or unexported fields
}

func NewRenderer

func NewRenderer(stdout, stderr io.Writer, jsonMode, noColor, quiet bool, format string) *Renderer

func (*Renderer) AlwaysWarn

func (r *Renderer) AlwaysWarn(msg string)

AlwaysWarn writes a warning to stderr even in --json mode.

func (*Renderer) Answer

func (r *Renderer) Answer(key, value string)

Answer is the durable receipt for an answered prompt. Interactive forms erase themselves when they complete, so every decision the user makes is echoed back as a permanent transcript line.

func (*Renderer) Blank

func (r *Renderer) Blank()

Blank prints an empty separator line between logical sections.

func (*Renderer) Error

func (r *Renderer) Error(msg string)

func (*Renderer) Field

func (r *Renderer) Field(key, value string, note ...string)

Field prints an aligned key/value line for status blocks: dim key, normal value, indented under a Title.

func (*Renderer) Hint

func (r *Renderer) Hint(msg string)

Hint is guidance about what to do next — a whole line, dimmed, so it never competes with results.

func (*Renderer) Info

func (r *Renderer) Info(msg string)

func (*Renderer) IsJSON

func (r *Renderer) IsJSON() bool

func (*Renderer) Lead

func (r *Renderer) Lead(text string)

Lead is the orienting sentence(s) under a Title: what this flow is for and what the pieces mean, in plain language. Normal weight — education is content, not chrome — and wrapped for readability. One Lead per flow, at the top; stay terse afterwards.

func (r *Renderer) Link(url string) string

Link renders a URL as a clickable, underlined brand-accent hyperlink. Falls back to the plain URL when color is off (our proxy for a dumb/non-interactive terminal), so nothing leaks escape codes into piped or --no-color output.

func (*Renderer) LinkLine

func (r *Renderer) LinkLine(url string)

LinkLine prints a single indented, clickable link on its own line (stderr chatter), for "open this URL" moments in guided flows.

func (*Renderer) LinkText

func (r *Renderer) LinkText(label, url string) string

LinkText renders a clickable hyperlink (OSC 8) with a custom label instead of the raw URL, so long auth URLs don't dominate the output. With color off it falls back to "label (url)" so the URL stays copyable.

func (*Renderer) NoColor

func (r *Renderer) NoColor() bool

NoColor reports whether all ANSI output is disabled (--no-color or NO_COLOR), so callers composing their own escapes (e.g. OSC 8 hyperlinks) can skip them.

func (*Renderer) Notice

func (r *Renderer) Notice(lines ...string)

Notice renders a must-read callout: a colored bar block that stands out from the narration without being an error. Use for trust and safety statements at the moment they matter (e.g. before a credential prompt) — never dim these.

func (*Renderer) Paint

func (r *Renderer) Paint(t Tone, text string) string

Paint renders text in the given tone, unstyled when color is disabled.

func (*Renderer) Panel

func (r *Renderer) Panel(lines ...string) string

Panel wraps pre-styled lines in a rounded border box sized to fit its content. The frame is drawn uncolored when color is disabled.

func (*Renderer) Plan

func (r *Renderer) Plan(steps []string)

Plan renders the guided-command plan: a titled, numbered list of the steps a consequential flow is about to take. Keep steps terse and state-aware — list what will happen this run, not every possibility.

func (*Renderer) Render

func (r *Renderer) Render(v any) error

Render writes the primary result. JSON mode emits a stable envelope so agents can rely on shape. When --format is set alongside --json, the envelope is fed through gojq; each output value is emitted on its own line.

func (*Renderer) RenderCard

func (r *Renderer) RenderCard(c Card) error

RenderCard writes a Card to stdout in TTY mode or hands off to Render() in JSON mode. Status helpers continue to go to stderr.

func (*Renderer) RenderJSON

func (r *Renderer) RenderJSON(v any) error

RenderJSON always emits JSON, even in human mode — for commands whose output IS structured data (rc schema, rc commands, SDK payload dumps).

func (*Renderer) RenderTable

func (r *Renderer) RenderTable(t Table) error

func (*Renderer) Stderr

func (r *Renderer) Stderr() io.Writer

Stderr returns the chatter writer so guided-flow components (tui.Flow, ledger, rail prompts) render to the same stream as the rest of the CLI — and are captured in tests — instead of writing to os.Stderr directly.

func (*Renderer) Success

func (r *Renderer) Success(msg string)

Message methods sanitize their text: call sites routinely interpolate API values, and stderr is still a terminal.

func (*Renderer) Title

func (r *Renderer) Title(msg string)

Title starts a visually distinct section: a brand-colored bar plus a bold heading, preceded by a blank line so output breathes.

func (*Renderer) Warn

func (r *Renderer) Warn(msg string)

type Table

type Table struct {
	Columns []string
	Rows    [][]string
	// Raw is the structured value returned under --json. Required so JSON
	// output stays useful (a table of strings would lose typing).
	Raw any
}

Table is a column-oriented view rendered as a simple aligned table in TTY mode and as the raw underlying value in --json mode. Header order is the caller's responsibility — map iteration would be unstable.

type Tone

type Tone int

Tone is a semantic style for callers that compose their own output.

const (
	ToneAccent  Tone = iota // brand landmark (bar, identity)
	ToneTitle               // bold section label
	ToneDim                 // secondary/description text
	ToneSuccess             // affirmative (logged in)
	ToneCommand             // a command the user can type
	ToneLink                // a URL to open
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL