{"content":"# aux4/render\n\nRender a JSON array from standard input as a human-readable view. Pipe the raw JSON output of any aux4 command through aux4 render to get a tidy list or table in the terminal.\n\naux4/render provides five commands:\n\n- aux4 render list — an MUI-List-style view (icon, primary, secondary, and a right-aligned badge label).\n- aux4 render table — a table view that delegates to aux4/2table.\n- aux4 render csv — a CSV view that delegates to aux4/2table.\n- aux4 render kv — a flat key=value (dotenv-style) view that flattens each record's fields, using the same structure grammar as render table/csv.\n- aux4 render yaml — a YAML view that preserves nesting, using the same structure grammar as render kv (but keeps the selection nested instead of flattening it).\n\n## Installation\n\n``bash\naux4 aux4 pkger install aux4/render\n`\n\n## System Dependencies\n\nThis package runs on Node.js. If it is not already available, the installer can provision it:\n\n- [brew](/r/public/packages/aux4/system-installer-brew)\n- [linux](/r/public/packages/aux4/system-installer-linux)\n\nThe render table and render csv commands delegate to aux4 2table, which is declared as a package dependency and installed automatically.\n\n## Quick Start\n\nGiven a JSON array on standard input, render it as a list:\n\n`bash\ncat people.json | aux4 render list --primary '$firstName $lastName' --secondary role --badge status\n`\n\n`text\n Ada Lovelace active\n Engineer\n\n Linus Torvalds away\n Maintainer\n`\n\nOr as a table:\n\n`bash\ncat people.json | aux4 render table firstName,lastName,role\n`\n\n`text\n firstName lastName role\n Ada Lovelace Engineer\n Linus Torvalds Maintainer\n`\n\n## Using it as a command's render\n\nThe main reason aux4/render exists is to be the [render](https://docs.aux4.io/render) of another .aux4 command. A command captures raw JSON into its response (with a json: executor), and its render field pipes that response through aux4 render **only when the output goes to a terminal** — a piped or redirected run still gets the raw JSON untouched, so scripts keep structured data.\n\n`json\n{\n \"profiles\": [\n {\n \"name\": \"main\",\n \"commands\": [\n {\n \"name\": \"servers\",\n \"execute\": [\n \"json:curl -s https://api.example.com/servers\"\n ],\n \"render\": {\n \"tty\": \"aux4 render list --primary name --icon 'status[ONLINE:🟢,OFFLINE:🔴]' --secondary region --badge status\",\n \"table\": \"aux4 render table name,region,status\"\n },\n \"help\": {\n \"text\": \"list the servers\"\n }\n }\n ]\n }\n ]\n}\n`\n\n- aux4 servers in a terminal renders the list — the tty entry runs automatically.\n- aux4 servers --render table selects the table format explicitly.\n- aux4 servers | jq . (piped, not a TTY) skips rendering and emits the raw JSON.\n\nThe execute step must **capture** its output into the response — use a json: or nout: executor. (On aux4 5.1.31+ a bare shell command works too: the core auto-silences the execute step whenever a render field is present, so the raw output isn't printed alongside the rendered view.) See the [Render documentation](https://docs.aux4.io/render) for the full mechanics of the render field, --render <name> selection, and TTY auto-detection.\n\n## Field interpolation\n\nEvery render list template flag (--icon, --primary, --secondary, --badge) is resolved against the current record. The forms below are tried in order — a value-map (brackets) first, then a named transform (bare colon), then the base bare-field / literal / $-interpolation rules:\n\n- **Value-map — field[VALUE:label,VALUE2:label2,...]** — look up the record's field, then map **its value** through the bracketed table. --icon 'status[TODO:📋,IN_PROGRESS:🔧,DONE:✅]' shows a per-status emoji; --secondary 'status[TODO:To Do,IN_PROGRESS:In Progress,DONE:Done]' shows a friendly label. When the record's value is **not** a key in the map, the raw field value is shown unchanged (never blank, never an error). The bracket grammar is the same comma/colon grammar used by render kv (VALUE:label pairs, optionally quoted labels).\n- **Value-format — field{format:TYPE,option:value,...}** — render the record's field value through the shared value formatter (see [Value formatting](#value-formatting) below). --badge 'price{format:currency,currency:USD}' shows $1,234.50; --secondary 'createdAt{format:datetime}' shows a localized date+time. Empty or un-parseable values fall back to the raw value (never NaN/Invalid Date).\n\n > **Breaking change (render list):** the old field:transform colon syntax (field:number, field:date, field:case, …) has been **removed**. A bare colon is no longer a transform — use field{format:...} instead. There is no replacement for the old case transform.\n- **No $ in the value** — if the record has a field with that name, the field's value is used (2table-style). --secondary date renders record.date. Dot notation works for nested fields: --secondary address.city. If the record has **no** such field, the whole string is used as a literal constant on every row, so --icon 😀 prints 😀 on each row and --badge done prints done on each row.\n- **One or more $field tokens** — each $field is replaced with that record field (empty string when missing) and the rest of the string is kept literal. --primary '$firstName $lastName' renders the two fields joined by a space.\n\nThe tokens are deliberately bare $field, **not** ${...}. This keeps them clear of aux4's own execute-line ${...} substitution. Single-quote the flag in your shell (--primary '$firstName $lastName') so the shell itself does not expand $field before aux4 sees it.\n\n**Note:** field lookup takes precedence over the literal fallback. --icon emoji uses the emoji field when the record has one; only when no emoji field exists is the string rendered literally. A value-map always has brackets and a value-format always has braces ({format:...}), so a plain literal (even one containing a colon) is never mistaken for either.\n\n## Value formatting\n\nEvery render command supports a per-field {format:...} modifier that renders a raw value as a formatted display string. The formatter is **vendored from [aux4/2table](https://github.com/aux4/2table)** (lib/ValueFormatter.js) so render and 2table format values identically; for render table / render csv the modifier is forwarded straight through to aux4 2table, which does the formatting.\n\nGrammar: field{format:TYPE,option:value,option:value}. Options may be separated by , or ;. Commas inside the braces stay grouped, so a multi-option modifier is never split apart from the rest of the structure.\n\nFormat types:\n\n| Type | Renders | Example (locale:en-US) |\n|------|---------|--------------------------|\n| number | Grouped number | 1234567.89 → 1,234,567.89 |\n| currency | Currency (ISO code, default USD) | 1234.5 → $1,234.50 |\n| percent | Percentage (value treated as a **ratio**) | 0.1234 + decimals:2 → 12.34% |\n| date | Localized date | 1990-05-01 → May 1, 1990 |\n| time | Localized time | 2026-07-15T02:30:00Z → 2:30:00 AM (UTC) |\n| datetime | Localized date + time | 2026-07-15T02:30:00Z → Jul 15, 2026, 2:30 AM (UTC) |\n\nOption keys:\n\n- decimals — fixed fraction digits (number, currency, percent).\n- currency — ISO currency code for currency (default USD).\n- locale — BCP 47 locale (e.g. en-US). Defaults to the host locale.\n- style — unified temporal style: short | medium | long | full. Sets the date style for date, the time style for time, and **both** for datetime.\n- dateStyle / timeStyle — per-part overrides. Precedence per part: explicit part style > style > built-in default (date medium, time medium, datetime = date medium + time short).\n\nEmpty values render as an empty string; values that cannot be parsed for the requested type fall back to the raw value unchanged — never NaN or Invalid Date.\n\nPer-command usage:\n\n- **render kv** — field{format:...} in the structure formats the emitted value: aux4 render kv 'name,price{format:currency,currency:USD}' → price=$1,234.50.\n- **render yaml** — a formatted leaf becomes a display **string** (formatting intentionally turns a typed value into a string); unformatted fields keep their original type and nesting.\n- **render list** — any template flag accepts field{format:...}: aux4 render list --primary name --badge 'price{format:currency}'.\n- **render table / render csv** — the modifier is passed through verbatim to aux4 2table, so aux4 render table 'name,price{format:currency,currency:USD}' renders a formatted column. Requires a current aux4/2table that supports {format:...}.\n\n`bash\necho '[{\"name\":\"Widget\",\"price\":1234.5,\"rate\":0.2,\"ts\":\"2026-07-15T02:30:00Z\"}]' \\\n | aux4 render kv 'name,price{format:currency,currency:USD,locale:en-US},rate{format:percent,decimals:0,locale:en-US},ts{format:datetime,style:short,locale:en-US}'\n`\n\n`text\nname=Widget\nprice=$1,234.50\nrate=20%\nts=7/15/26, 2:30 AM\n`\n\n## Input handling\n\nAll render commands read JSON from standard input and handle these cases consistently:\n\n- **JSON array** — rendered as usual, one row per element.\n- **A single JSON object** (not wrapped in an array) — treated as a one-item array and rendered normally.\n- **An empty array ([])** — a clean no-op: nothing is printed and the command exits 0.\n- **Invalid / non-JSON input** — the command prints a clear error to stderr and exits 1.\n\n## Commands\n\n### aux4 render list\n\nReads a JSON array from stdin and prints one block per record.\n\nOptions:\n\n- --primary <template> — Primary line (required). Bare field name or $field interpolation. A list item's primary is a single line: when it is too long to fit the terminal width (accounting for the icon prefix and the badge, if present), it is truncated with a trailing … rather than wrapping.\n- --secondary <template> — Secondary line, printed beneath the primary and indented to align under it.\n- --icon <template> — Rendered before the primary line.\n- --badge <template> — Right-aligned on the primary line to the terminal width (80 columns when not a TTY, e.g. in a pipe). Plain text, not interactive.\n\nRecords are separated by a blank line. In a real terminal the primary text is printed in yellow to stand out; the icon, secondary, and badge stay uncolored. The plain-text examples below do not show the color.\n\nInput (people.json):\n\n`json\n[\n { \"firstName\": \"Ada\", \"lastName\": \"Lovelace\", \"role\": \"Engineer\", \"status\": \"active\" },\n { \"firstName\": \"Linus\", \"lastName\": \"Torvalds\", \"role\": \"Maintainer\", \"status\": \"away\" }\n]\n`\n\n`bash\ncat people.json | aux4 render list --primary '$firstName $lastName' --secondary role --badge status\n`\n\n`text\n Ada Lovelace active\n Engineer\n\n Linus Torvalds away\n Maintainer\n`\n\nPer-row icon from a field:\n\n`bash\ncat people.json | aux4 render list --icon emoji --primary '$firstName $lastName'\n`\n\nLiteral icon on every row (no emoji field exists, so the value is used as-is):\n\n`bash\ncat people.json | aux4 render list --icon 😀 --primary '$firstName $lastName'\n`\n\nStatus-driven icon and label with a value-map, plus a formatted timestamp badge:\n\n`bash\necho '[{\"title\":\"Ship release\",\"status\":\"IN_PROGRESS\",\"createdAt\":\"2026-07-15T14:30:00Z\"}]' \\\n | aux4 render list \\\n --primary title \\\n --icon 'status[TODO:📋,IN_PROGRESS:🔧,DONE:✅]' \\\n --secondary 'status[TODO:To Do,IN_PROGRESS:In Progress,DONE:Done]' \\\n --badge 'createdAt{format:datetime,locale:en-US}'\n`\n\n`text\n 🔧 Ship release Jul 15, 2026, 10:30 AM\n In Progress\n`\n\nAn unmapped value falls back to the raw field value (BLOCKED has no entry in the map):\n\n`bash\necho '[{\"title\":\"Investigate outage\",\"status\":\"BLOCKED\"}]' \\\n | aux4 render list --primary title --icon 'status[TODO:📋,IN_PROGRESS:🔧,DONE:✅]'\n`\n\n`text\n BLOCKED Investigate outage\n`\n\nValue formatting — format a count as currency (see [Value formatting](#value-formatting)):\n\n`bash\necho '[{\"name\":\"Widgets\",\"total\":12000}]' \\\n | aux4 render list --primary name --badge 'total{format:currency,currency:USD,locale:en-US}'\n`\n\n`text\n Widgets $12,000.00\n`\n\n### aux4 render kv\n\nReads a JSON array from stdin and prints one key=value (dotenv-style) line per resolved field per record — a flat, greppable view of every record's fields.\n\nIt uses the **same structure grammar** as render table / render csv / aux4 2table:\n\n- structure (positional, optional) — comma-separated fields with the usual grammar:\n - name,age,city — select those top-level fields.\n - address[street,city] — nested groups flatten to **dotted keys** (address.street=..., address.city=...).\n - field:\"Label\" (or field:Label) — rename the emitted key. address[street:\"Street Address\"] emits Street Address=Main St; the label replaces the whole key.\n - A bare object field with no brackets (address) emits the sub-object as a single JSON value.\n - Column-width modifiers like {width:20} are accepted but silently ignored, so a structure you also use with table/csv can be pasted unchanged.\n- --index <N> (optional) — 0-based index selecting a **single** record from the top-level array before anything else runs. The selected record is rendered on its own with no index prefix (see array flattening below). An out-of-range index (N ≥ record count, or N < 0) prints a clear error to stderr and exits 1. Omit it to render every record.\n\nOmit the structure to auto-flatten **every** field recursively into dotted keys.\n\n### Array flattening\n\nArrays are flattened uniformly at **every** level — including the top-level array of records — into **indexed dotted keys**, using each element's position as a path segment:\n\n- A nested array field tags: [\"a\", \"b\"] becomes tags.0=a, tags.1=b (a single-element array is still indexed: tags.0=a).\n- Arrays of arrays recurse the same way: matrix: [[1, 2], [3, 4]] becomes matrix.0.0=1, matrix.0.1=2, matrix.1.0=3, matrix.1.1=4.\n- When the top-level array has **more than one** record, each record's keys are prefixed with its 0-based index (0.name=Alice, 1.name=Bob), so record boundaries are unambiguous.\n- **Exception:** a single record gets **no** index prefix — a lone JSON object, a one-element array, or a record picked with --index N is treated as *the* record (name=Alice, not 0.name=Alice).\n\nInput (person.json):\n\n`json\n[\n { \"name\": \"Alice\", \"address\": { \"street\": \"Main St\", \"city\": \"NYC\" }, \"tags\": [\"a\", \"b\"] }\n]\n`\n\nExplicit structure with a nested group and a rename:\n\n`bash\ncat person.json | aux4 render kv 'name,address[street,city:\"City\"]'\n`\n\n`text\nname=Alice\naddress.street=Main St\nCity=NYC\n`\n\nNo structure — auto-flatten every field (arrays become indexed keys):\n\n`bash\ncat person.json | aux4 render kv\n`\n\n`text\nname=Alice\naddress.street=Main St\naddress.city=NYC\ntags.0=a\ntags.1=b\n`\n\nMultiple records — each record is prefixed with its index:\n\n`bash\necho '[{\"name\":\"Alice\",\"tags\":[\"a\",\"b\"]},{\"name\":\"Bob\",\"tags\":[\"c\"]}]' | aux4 render kv\n`\n\n`text\n0.name=Alice\n0.tags.0=a\n0.tags.1=b\n1.name=Bob\n1.tags.0=c\n`\n\nSelect a single record with --index (rendered with no prefix):\n\n`bash\necho '[{\"name\":\"Alice\",\"tags\":[\"a\",\"b\"]},{\"name\":\"Bob\",\"tags\":[\"c\"]}]' | aux4 render kv --index 1\n`\n\n`text\nname=Bob\ntags.0=c\n`\n\n### aux4 render yaml\n\nReads a JSON array from stdin and prints it as **YAML**, preserving nesting. It shares the **same structure grammar** as render kv for selecting fields, but unlike kv — which flattens the selection into dotted key=value lines — yaml keeps the selection **nested** in the output.\n\n- structure (positional, optional) — comma-separated fields with the usual grammar:\n - name,age,city — select those top-level fields.\n - address[street,city] — nested groups stay **nested**: an address: mapping with street/city under it (**not** flat address.street/address.city keys).\n - field:\"Label\" (or field:Label) — rename the emitted key. address[street,city:\"City\"] emits a City: key inside the nested address mapping.\n - Applying a field[...] group to an array projects each element through the group, producing a YAML sequence of nested mappings.\n - Column-width modifiers like {width:20} are accepted but silently ignored, so a structure you also use with table/csv/kv can be pasted unchanged.\n- --index <N> (optional) — 0-based index selecting a **single** record from the top-level array before serialization. The selected record is rendered as a single YAML mapping. An out-of-range index (N ≥ record count, or N < 0) prints a clear error to stderr and exits 1. Omit it to render every record.\n\nOmit the structure to dump the full record as-is — there is no flattening step, since YAML represents nested structure natively.\n\nA single record (a lone JSON object, a one-element array, or a record picked with --index N) is dumped as a single YAML **mapping**; multiple records are dumped as a YAML **sequence** of mappings.\n\nInput (person.json):\n\n`json\n[\n { \"name\": \"Alice\", \"address\": { \"street\": \"Main St\", \"city\": \"NYC\", \"zip\": \"10001\" } }\n]\n`\n\nExplicit structure with a nested group and a rename (nesting preserved, zip dropped):\n\n`bash\ncat person.json | aux4 render yaml 'name,address[street,city:\"City\"]'\n`\n\n`text\nname: Alice\naddress:\n street: Main St\n City: NYC\n`\n\nNo structure — dump the full record:\n\n`bash\ncat person.json | aux4 render yaml\n`\n\n`text\nname: Alice\naddress:\n street: Main St\n city: NYC\n zip: '10001'\n`\n\nMultiple records — dumped as a YAML sequence:\n\n`bash\necho '[{\"name\":\"Alice\"},{\"name\":\"Bob\"}]' | aux4 render yaml\n`\n\n`text\n- name: Alice\n- name: Bob\n`\n\nSelect a single record with --index (rendered as a single mapping):\n\n`bash\necho '[{\"name\":\"Alice\"},{\"name\":\"Bob\"}]' | aux4 render yaml --index 1\n`\n\n`text\nname: Bob\n`\n\n### aux4 render table\n\nReads a JSON array from stdin and renders an ASCII table by delegating to aux4 2table. The full 2table structure language is supported (simple columns, nested objects and arrays, renaming, fixed widths, auto-structure).\n\nOptions:\n\n- table (positional) — The table structure (column list) to output. Omit to auto-generate.\n- --lineNumbers <true|false> — Add a first column with line numbers starting from 1 (default: false). Forwarded to 2table.\n- --showInvalidLines <true|false> — Show invalid lines as <invalid line> instead of skipping them (default: false). Forwarded to 2table.\n\nASCII table:\n\n`bash\ncat people.json | aux4 render table firstName,lastName,role\n`\n\n`text\n firstName lastName role\n Ada Lovelace Engineer\n Linus Torvalds Maintainer\n`\n\nLine numbers and invalid-line handling (forwarded to 2table):\n\n`bash\ncat data.json | aux4 render table name,age --lineNumbers true --showInvalidLines true\n`\n\n`text\n # name age\n 1 Alice 30\n 2 <invalid line>\n 3 Charlie 35\n`\n\n**Note:** render table requires the aux4/2table package. It is declared as a dependency and installed automatically. If aux4 2table is not available at runtime, the command fails with a clear message rather than doing nothing.\n\n### aux4 render csv\n\nReads a JSON array from stdin and renders CSV by delegating to aux4 2table --format csv. The full 2table structure language is supported (simple columns, nested objects and arrays, renaming, auto-structure). Output is RFC 4180 CSV: a field containing a comma, double quote, or newline is quoted automatically.\n\nOptions:\n\n- table (positional) — The table structure (column list) to output. Omit to auto-generate.\n- --lineNumbers <true|false> — Add a first column with line numbers starting from 1 (default: false). Forwarded to 2table.\n- --showInvalidLines <true|false> — Show invalid lines as <invalid line> instead of skipping them (default: false). Forwarded to 2table.\n\nInput (people.json):\n\n`json\n[\n { \"name\": \"Alice\", \"age\": 30 },\n { \"name\": \"Bob, Jr.\", \"age\": 25 }\n]\n`\n\n`bash\ncat people.json | aux4 render csv name,age\n`\n\n`text\nname,age\nAlice,30\n\"Bob, Jr.\",25\n`\n\nA value containing a comma (Bob, Jr.) is quoted automatically, because delegation goes through 2table's RFC 4180 CSV renderer.\n\n**Note:** render csv requires the aux4/2table package. It is declared as a dependency and installed automatically. If aux4 2table` is not available at runtime, the command fails with a clear message rather than doing nothing.\n\n## License\n\nThis package is licensed under the Apache License 2.0.\n\n\n"}