Skip to content
Kona

Console IO & prompts

All Kona terminal CLIs share one Termwind-inspired console style: brand colors (Mocha #6F4E37 / deep espresso #3D2914), success/info/warn/error markers, boxes, two-column lists, tables, and progress bars.

API Module Role
org.konaframework.support.console.ConsoleIO kona-support Canonical implementation
org.konaframework.grind.GrindIO kona-grind Wraps ConsoleIO with quiet / verbose filtering
Helpers on ConsoleCommand kona-grind info, success, warn, error, table, progress, …
org.konaframework.grind.Prompts kona-grind Interactive ask / confirm / choice

Do not print user-facing output with raw println / unstyled System.out in commands; use these APIs.

ConsoleIO.detectColor (also used by GrindIO.detectColor):

Env Effect
NO_COLOR Disable ANSI (any value present)
FORCE_COLOR=1 (any non-0) Force ANSI even when TERM=dumb
FORCE_COLOR=0 Keep color off
TERM=dumb or missing TERM Color off unless forced
enum class Verbosity { QUIET, NORMAL, VERBOSE }

GrindIO.fromOptions(options) sets verbosity from CLI flags:

Flags Verbosity
--quiet / -q QUIET
--verbose / -v VERBOSE
neither NORMAL

Quiet wins if both quiet and verbose keys appear (checked first).

Method Quiet Normal Verbose
writeln / write / line / info / success / warn / title / subtitle / twoColumn / box / progress / table suppressed prints prints
error always prints (stderr) prints prints
verbose suppressed suppressed prints (dim)

Construct with optional PrintStream out/err and a color boolean:

val console = ConsoleIO()
console.info("Working…")
console.success("Done")
console.warn("Slow path")
console.error("Could not connect")
console.title("Report")
console.subtitle("Section")
console.twoColumn("serve", "Run the HTTP server")
console.box("grind", listOf("Kona app CLI"))
console.progress(3, 10, "users")
console.table(
listOf("id", "email"),
listOf(listOf("1", "ada@example.com"), listOf("2", "bea@example.com")),
)
Method Behavior
writeln / write / line Raw stdout (still subject to GrindIO quiet when wrapped)
info Cyan ℹ marker + message
success Green ✓
warn Yellow !
error Red ✗ on stderr
title Bold primary heading + dim underline (max 48)
subtitle Accent-colored line
twoColumn(left, right, width=22) Primary bold left column + dim right
box(title, lines) Framed block; content width clamped roughly 24–56
progress(current, total, label="") 28-cell bar + percent; total must be > 0; current clamped to [0, total]
table(headers, rows) Aligned columns by plain text width (ANSI stripped); headers non-empty; each row must match header width
paint(codes, text) Apply ANSI when color is on
plainLength(text) Length with ANSI sequences removed

Public ANSI constants on ConsoleIO: RESET, BOLD, DIM, RED, GREEN, YELLOW, CYAN, PRIMARY, ACCENT.

Same high-level helpers as above, plus:

val io = GrindIO.fromOptions(mapOf("verbose" to "true"))
io.verbose("only with -v")
io.subtitle("Detail")
io.twoColumn("left", "right")
io.box("Title", listOf("line"))

Inside a ConsoleCommand, prefer the bound helpers (they use the runner’s GrindIO):

info("Working…")
success("Done")
warn("Slow path")
error("Could not connect")
title("Report")
line()
line("plain")
table(
listOf("id", "email"),
listOf(listOf("1", "ada@example.com"), listOf("2", "bea@example.com")),
)
progress(3, 10)
progress(3, 10, "users")
verbose("only with -v")

ConsoleCommand.progress has overloads with and without a label. There is no box / twoColumn / subtitle helper on ConsoleCommand itself — use io.box(...) / io.twoColumn(...) / io.subtitle(...) when you need them.

Use Prompts for interactive input. Injectable streams keep tests non-interactive:

val prompts = Prompts()
val name = prompts.ask("Name", default = "Kona")
val ok = prompts.confirm("Continue?", default = false)
val pick = prompts.choice("Driver", listOf("array", "redis"), defaultIndex = 0)
Method Behavior
ask(question, default=null) Free text; empty line uses default when set; otherwise returns empty string
confirm(question, default=false) Hint Y/n or y/N; empty → default; y / yes → true
choice(question, options, defaultIndex=0) Numbered list; empty → default; accepts 1-based index or case-insensitive label; invalid input → default

choice requires a non-empty options list and a defaultIndex in range. Styling uses ConsoleIO primary/accent/dim paints on the prompt streams you inject (default: System.in / System.out).

Built-ins such as serve (port conflict) and key:generate (production overwrite) use Prompts internally.

Code Constant Meaning
0 ConsoleCommand.SUCCESS Success (also when --isolated skips a busy command)
1 ConsoleCommand.FAILURE Usage errors, missing projects, failed generators, unbound services, unknown commands

Scripts and CI should treat any non-zero exit as failure. Built-ins typically return 0/1 the same way; uncaught GrindException / CadenceException in GrindApplication become error lines and exit 1.

ConsoleTester captures stdout only while running GrindApplication — see HTTP & console tests. Assertions:

Method Behavior
assertOk() Exit code 0
assertExit(code) Exact exit code
assertSee(text) Stdout contains substring
assertDontSee(text) Stdout does not contain substring

Because error(...) writes to stderr, failed error messages may not appear in ConsoleResult.output. Assert on exit codes for failure paths, or inject streams when testing Prompts / ConsoleIO directly (as the module tests do).

  • Quiet hides progress and tables — do not rely on progress bars in -q CI logs.
  • table row width — mismatched column counts throw; normalize rows first.
  • progress total — must be positive or ConsoleIO throws.
  • Color in pipes — honor NO_COLOR in automation; do not assume ANSI in captured test output.
  • Global System.out swap — Grind.callSilently and ConsoleTester replace stdout; avoid nested concurrent calls that depend on the real stream.