benday 0.1.0

Crisp terminal charts from a Vega-Lite-style JSON spec, built for agents to call
# benday

Terminal charts from a Vega-Lite-style JSON spec, drawn in braille dots.
Built for AI agents: pipe query results in, get a chart the human can read
in the transcript.

![Demo: rendering charts from JSON specs in the terminal](assets/demo.gif)

## Install

```sh
cargo install benday
```

Or from source: `git clone https://github.com/fwojciec/benday && cargo install --path benday/crates/benday-cli`.

## Use

Pipe rows in, pass a spec:

```sh
echo '{"columns":[{"name":"day","type":"STRING"},{"name":"n","type":"INT64"}],
       "rows":[["mon",32],["tue",78],["wed",51]]}' \
  | benday --spec '{"mark":"bar","encoding":{"x":{"field":"day"},"y":{"field":"n"}}}'
```

The spec can instead carry its data inline and arrive on stdin by itself.
`benday --help` documents everything: the spec grammar, both stdin shapes,
the bar rules, and worked examples — an agent needs nothing else.

## The spec

A strict subset of Vega-Lite:

```jsonc
{
  "data"?: {                                   // optional — omit to pipe rows in
    "values": [ /* one JSON object per row */ ]           // tidy rows, OR
    // "columns": [ {"name":"day","type":"STRING"} ], "rows": [ ["mon",32] ]  // columnar
  },
  "mark": "bar" | "line" | "point" | "area",
  "encoding": {
    "x":      { "field": "...", "type"?: "quantitative" | "nominal" | "ordinal" },
    "y":      { "field": "...", "aggregate"?: "sum" | "mean" | "median" | "min" | "max" | "count" },
    "color"?: { "field": "..." }   // series split, or bar grouping
  },
  "title"?: "...", "width"?: 72, "height"?: 13   // plot area, in cells
}
```

Types are inferred from the data when omitted; a declared column type
(`INT64`, `DATE`, …) beats inference, and an explicit spec `"type"` beats
both. Two bar rules follow from the encoding, with no extra flags:

- **Orientation.** Quantitative `x` + categorical `y` draws *horizontal*
  bars — built for rankings: one row per bar, height sized to the category
  count, names in a label column. Categorical `x` + quantitative `y` stays
  vertical.
- **Grouping.** `color` naming a *third* field splits each category into a
  grouped cluster, one bar per value, with a legend. `color` naming the
  category field itself just tints the bars.

Rows chart in arrival order, so `ORDER BY` in the producing query is the
sort. Unknown spec fields are errors that name the fix, never silently
ignored — a silently wrong chart is the one failure a caller can't detect.

## Data on stdin

With `--spec`/`--spec-file`, stdin carries the data, auto-detected between
two shapes:

- **Columnar envelope**`{"columns": [...], "rows": [[...]]}`, the shape
  an MCP query tool emits as `structuredContent`. Extra keys are ignored,
  so pipe it straight in; `truncated` and `total_rows` flow to `--meta`.
- **Bare array of row objects**`[{"day":"mon","n":32}, ...]`.

Declared dates chart as ordinal (ISO strings sort chronologically); date
bucketing and label formatting belong to the SQL that produced the rows.

## Flags and output

`--marker braille|octant` · `--bar-style dots|blocks` · `--theme
benday|lichtenstein|rotogravure` · `--width`/`--height` · `--no-color` ·
`--meta`

Output is deterministic and color stays on when piped — the transcript is
the display. Errors are JSON on stderr with the fix in the message; exit
`2` = invalid spec, `3` = data doesn't fit the encoding. `--meta` prints
scale domains, resolved series colors, and dropped-row counts to stderr,
so a caller can verify what was drawn without parsing dot art.

## Development

Two-stage pipeline: `compile(spec, data) -> Scene` makes every data- and
layout-dependent decision; `rasterize(scene) -> glyphs` turns the geometry
into cells. A new chart type is compiler work; a new visual style is
rasterizer work. Three test layers: a spec→scene snapshot corpus
(`crates/benday-core/tests/cases/`), rasterizer unit tests, and a rendered
glyph gallery. `make validate` runs exactly what CI runs. Design records
live in `docs/plans/`.

## Status

Works: bars (vertical, horizontal, grouped), line, point, area,
multi-series legends, aggregation, type inference, three themes. Planned:
stacked bars, value labels at bar ends, `benday schema` (JSON Schema
output). Deliberately absent: temporal scales and sort grammar — SQL owns
sorting, bucketing, and date formatting. Negative bars are a hard error,
not a silent miss.

Named for [Ben-Day dots](https://en.wikipedia.org/wiki/Ben_Day_process):
images composed from a raster of small marks, which is what terminal cells
are. MIT. Octant glyph table derived from
[ratatui](https://github.com/ratatui/ratatui) (MIT).