Give Any Command a Beautiful Terminal UI

Introduction

A command that dumps raw JSON is fine for a machine and painful for a human. This post uses aux4/render to turn a JSON array into a readable terminal view — a formatted table, then a list with status icons and currency badges — and finally wires it into a custom command's render field so it shows a polished UI in a terminal but still emits plain JSON when piped into another tool.

Install the package

aux4 aux4 pkger install aux4/render

Some data to display

aux4 render reads a JSON array on stdin. We'll use a small task board — save it as tasks.json:

[
  { "title": "Design landing page", "owner": "Sally", "status": "DONE", "priority": "high", "budget": 4200.5 },
  { "title": "Ship billing API", "owner": "Marco", "status": "IN_PROGRESS", "priority": "high", "budget": 15750 },
  { "title": "Write onboarding docs", "owner": "Priya", "status": "TODO", "priority": "medium", "budget": 900 },
  { "title": "Investigate outage", "owner": "Chen", "status": "BLOCKED", "priority": "urgent", "budget": 0 },
  { "title": "Refactor auth module", "owner": "Aisha", "status": "IN_PROGRESS", "priority": "low", "budget": 3300 }
]

Step 1: a formatted table

Pass the columns you want and render table lays them out — aligned, with colored headers in a real terminal:

cat tasks.json | aux4 render table title,owner,status,priority,budget
 title                  owner  status       priority  budget
 Design landing page    Sally  DONE         high      4200.5
 Ship billing API       Marco  IN_PROGRESS  high       15750
 Write onboarding docs  Priya  TODO         medium       900
 Investigate outage     Chen   BLOCKED      urgent         0
 Refactor auth module   Aisha  IN_PROGRESS  low         3300

A raw 4200.5 isn't money, though. Attach a format to a column with {format:...}:

cat tasks.json | aux4 render table 'title,owner,status,budget{format:currency,currency:USD,locale:en-US}'
 title                  owner  status           budget
 Design landing page    Sally  DONE          $4,200.50
 Ship billing API       Marco  IN_PROGRESS  $15,750.00
 Write onboarding docs  Priya  TODO            $900.00
 Investigate outage     Chen   BLOCKED           $0.00
 Refactor auth module   Aisha  IN_PROGRESS   $3,300.00

Step 2: a list with icons and badges

render list gives a card-style view. --primary is the headline, --secondary the subtitle, --badge the right-aligned tag. The real power is two mini-syntaxes:

  • Value mapsfield[VALUE:label,...] — swap a raw value for something friendlier (an emoji, a proper label).
  • Value formatsfield{format:currency,...} — same formatting as the table.

Put them together: a status icon, a human-readable status subtitle, and the budget as a currency badge:

cat tasks.json | aux4 render list \
  --primary title \
  --icon 'status[TODO:📋,IN_PROGRESS:🔧,DONE:✅,BLOCKED:🚫]' \
  --secondary 'status[TODO:To Do,IN_PROGRESS:In Progress,DONE:Done,BLOCKED:Blocked]' \
  --badge 'budget{format:currency,currency:USD,locale:en-US}'
 ✅ Design landing page                                                 $4,200.50
   Done

 🔧 Ship billing API                                                  $15,750.00
    In Progress

 📋 Write onboarding docs                                                $900.00
    To Do

 🚫 Investigate outage                                                     $0.00
    Blocked

 🔧 Refactor auth module                                               $3,300.00
    In Progress

The same data, now scannable. --primary also interpolates fields with $--primary '$owner: $title' — and unmapped values fall through unchanged.

Step 3: other views

A single record as key/value (--index selects the row):

cat tasks.json | aux4 render kv --index 0
title=Design landing page
owner=Sally
status=DONE
priority=high
budget=4200.5

Or as YAML — selected fields across all records:

cat tasks.json | aux4 render yaml 'title,status'
- title: Design landing page
  status: DONE
- title: Ship billing API
  status: IN_PROGRESS
- title: Write onboarding docs
  status: TODO
- title: Investigate outage
  status: BLOCKED
- title: Refactor auth module
  status: IN_PROGRESS

render csv rounds out the set for spreadsheet exports.

Step 4: wire it into a command

Here's the payoff. Instead of typing that long render list every time, attach it to a command in an .aux4 file. The render field maps output modes to render commands — tty runs automatically when output is a terminal. Create .aux4:

{
  "profiles": [
    {
      "name": "main",
      "commands": [
        {
          "name": "tasks",
          "execute": ["json:cat tasks.json"],
          "render": {
            "tty": "aux4 render list --primary title --icon 'status[TODO:📋,IN_PROGRESS:🔧,DONE:✅,BLOCKED:🚫]' --secondary 'status[TODO:To Do,IN_PROGRESS:In Progress,DONE:Done,BLOCKED:Blocked]' --badge 'budget{format:currency,currency:USD,locale:en-US}'",
            "table": "aux4 render table title,owner,status,priority,budget"
          },
          "help": { "text": "List the project tasks." }
        }
      ]
    }
  ]
}

The json: executor captures the JSON into the command's response. Now run it in a terminal and render.tty fires automatically:

aux4 tasks
 ✅ Design landing page                                                 $4,200.50
   Done

 🔧 Ship billing API                                                  $15,750.00
    In Progress

 📋 Write onboarding docs                                                $900.00
    To Do

 🚫 Investigate outage                                                     $0.00
    Blocked

 🔧 Refactor auth module                                               $3,300.00
    In Progress

But pipe it — no terminal, no rendering — and you get the raw JSON back, untouched, ready for jq or another command:

aux4 tasks | jq '.[0]'
{
  "budget": 4200.5,
  "owner": "Sally",
  "priority": "high",
  "status": "DONE",
  "title": "Design landing page"
}

That's the whole trick: one command, a human-friendly UI for a person and clean data for a pipeline. Any other named mode is on demand — render.table here is aux4 tasks --render table:

aux4 tasks --render table
 title                  owner  status       priority  budget
 Design landing page    Sally  DONE         high      4200.5
 Ship billing API       Marco  IN_PROGRESS  high       15750
 Write onboarding docs  Priya  TODO         medium       900
 Investigate outage     Chen   BLOCKED      urgent         0
 Refactor auth module   Aisha  IN_PROGRESS  low         3300

Conclusion

aux4/render turns a JSON array into a table, a key/value block, YAML, CSV, or a MUI-style list with icons and currency badges — and its render field lets any command detect a terminal and show a polished UI there while staying pipe-friendly everywhere else. Give your commands a face without giving up their data.

See Also