Skip to content

Render query results as tables and charts with workbook links - #97

Open
ernestoongaro wants to merge 12 commits into
mainfrom
chart-and-stream-results
Open

ernestoongaro wants to merge 12 commits into
mainfrom
chart-and-stream-results

Conversation

@ernestoongaro

@ernestoongaro ernestoongaro commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Query results now render as readable tables in human mode, using the model's field labels and number formats. Queries that exceed the initial wait window are polled until completion.

  • Add --chart for terminal bar charts, with field selection and row limits.
  • Render pivoted queries as tables and charts, with shared measure scales across pivot columns.
  • Add --workbook to request an ephemeral workbook and display its link.
  • Preserve large integer values in unscaled formats, sanitize formatted terminal text, and reject invalid workbook bodies.

JSON-mode query streams continue to pass through unchanged. Explicit JSON output cannot be combined with --chart.

Validation: make build, go test ./..., go vet ./..., and git diff --check pass. Regression coverage includes integer precision, control characters in model formats, and null workbook bodies.

ernestoongaro and others added 4 commits September 12, 2026 15:21
…kbook

query run and query wait stream NDJSON with rows as base64 Arrow and, on
each job line, the model's metadata for every field: label, whether it is
a dimension or a measure, data type, and number format. The CLI now decodes
that stream (internal/result) for human output instead of passing it
through, and renders what the model says: labels as headers, columns in
query order, formats applied. If the first response's wait window elapses,
query/wait is polled until every job has finished; a failing poll reports
through the same error envelope as any API call, and rendering is buffered
so a failure leaves nothing on stdout.

--chart draws the same result as a bar chart: the first measure by the
first dimension, or any column named with --chart-value / --chart-label by
field name or label. --chart-style picks the look (bar, block, line, fill)
and --chart-rows caps the bar count. It is accepted only on commands whose
spec returns the stream and refused before any request otherwise; a
resultType in the body is dropped, with a note in human mode. Nothing is
inferred from column names or values; a query with no measure plots its
first numeric column. Headers sit over their columns.

--workbook asks for an ephemeral workbook and surfaces its link: under
human-rendered output, or on stderr so a passed-through CSV/XLSX or JSON
stdout stays the payload. Both flags live on the API command groups.

Formats follow the documented reference: number/percent/id/big/billions/
millions/thousands, the currency, accounting and financial families with
optional big and currency-code prefixes, Excel-style patterns including
quoted literals, scaling commas, E+00 and pos;neg;zero sections, and
d3-style date patterns on temporal dimensions. Field references and
conditional formats need data the stream lacks and fall back to a plain
readable number. Decimal Arrow columns decode to floats; failed jobs
report the API's error_type and error_message.

Human output takes the brand palette: Omni Pink bars and headers, Dark
Gray borders and axis, Orange for negative bars, rounded borders.

Also: JSON numbers in the generic human table keep every digit and never
render in scientific notation, cell truncation counts terminal cells
rather than bytes, and the spinner line is cleared fully on stop.
--format json is unchanged: the stream still passes through byte for
byte, and an explicitly requested JSON format refuses --chart.

Adds github.com/apache/arrow-go/v18 (~5 MB of binary) to read the stream.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgE3j1US29JGkhFmT3X3Bh
The query stream always carries rows in long form; the Omni app pivots
them client-side from the model_job's pivots and column_limit, which the
stream echoes. The decoder now keeps both, and Set.Pivot reshapes the rows:
the remaining dimensions as rows, a column per pivot value and measure,
pivot values in the stream's order (a value one row group lacks lands where
a later group places it), capped at the column limit. --format human
renders it as the app's table, pivot value over measure label.

--chart now draws the app's bar table rather than one measure by one
dimension: every dimension is a label column, so two-dimension rows no
longer read as repeats of the first, and every measure gets a column of
bars scaled to its own maximum. Pivoted, a measure's bars share one scale
across its pivot columns. --chart-value and --chart-label narrow it to one
measure or dimension; labels yield width first, then trailing columns are
dropped with a note so a row never wraps.

A bare field name now finds a column whose label reads differently
(--chart-value count finds "Deals Count").

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016X6VBPtgHzXK2fwVsBBwtt
- Strip control characters from API and warehouse text in human output
  (cell values, labels, JSON strings and keys, error details, the workbook
  link), so a value can't retitle the terminal or write the clipboard. A
  newline in a cell reads as a space instead of breaking the layout.
- Currency symbols in patterns are consumed whole: £ and ¥ are two bytes,
  and a trailing one panicked; £0.00 ate a digit; £#,##0 went unrecognized.
- # decimals show when they aren't trailing zeros (#,##0.## → 1,234.57).
- big picks its unit after rounding: 999,999.9 → 1.00M, not 1000.00K.
- Pivot columns merge the orders the row groups agree on and fall back to
  the pivot fields' own sort (descending when the query says so) for values
  no group relates, instead of appending them.
- An unknown --chart kind is refused before the query runs.
- The omitted-columns note counts measure columns, not pivot values.
- Uint64 values past MaxInt64 no longer wrap negative.
- A pivot chart with one measure names it once, and pivot values widen
  their column rather than being cut off.
- Trim stale and repetitive comments; regenerate the README chart examples
  from the renderer and document fill and --workbook in the flags table.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016X6VBPtgHzXK2fwVsBBwtt
ernestoongaro and others added 3 commits September 14, 2026 16:50
block looked the same as bar, and fill and line didn't earn their keep.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWypY6weErBPcs1YJSPuik
Bar is the only chart, so a kind value only cluttered the help.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWypY6weErBPcs1YJSPuik
Hiding dimensions without aggregating repeated row labels and misread the
data. Narrowing measures is safe, and on a pivot it lets more pivot values
fit, so --chart-value now names any number of them.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWypY6weErBPcs1YJSPuik
@n8agrin
n8agrin requested a balanced review from Copilot September 14, 2026 20:28
@n8agrin

n8agrin commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

can you post some screenshots of the rendered output?

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Precision loss, pivot collisions, unsafe error output, and incompatible flag handling can produce incorrect behavior.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds human-readable query result rendering, charts, pivot support, polling, and workbook links to the generated Omni CLI.

Changes:

  • Decodes Arrow NDJSON streams into formatted tables and charts.
  • Adds pivoting, polling, sanitization, and workbook-link handling.
  • Introduces result flags, supporting tests, documentation, and dependencies.
File summaries
File Description
README.md Documents query rendering and flags.
CLAUDE.md Updates architectural output guidance.
go.mod Adds Arrow and rendering dependencies.
go.sum Updates dependency checksums.
cmd/omni/main.go Wires flags, body preparation, and rendering.
cmd/omni/agent_help.go Documents new flags for agents.
cmd/omni/output.go Integrates chart-aware response handling.
cmd/omni/output_test.go Tests flags, bodies, links, and responses.
cmd/omni/spinner.go Clears completed spinner lines.
cmd/omni/stream.go Parses, polls, and renders query streams.
cmd/omni/stream_test.go Tests polling and stream failures.
internal/config/config.go Separates chosen and effective formats.
internal/openapi/generate.go Annotates stream responses and body fields.
internal/openapi/generate_test.go Tests generated annotations.
internal/output/bench_test.go Benchmarks result rendering.
internal/output/chart.go Implements terminal bar charts.
internal/output/chart_test.go Tests chart and pivot layouts.
internal/output/format.go Implements model-aware formatting.
internal/output/format_test.go Tests numeric and temporal formats.
internal/output/human.go Improves generic human rendering.
internal/output/human_test.go Tests formatting and sanitization.
internal/output/result.go Builds result tables and chart grids.
internal/output/sanitize.go Sanitizes terminal-bound text.
internal/output/style.go Defines shared output styles.
internal/result/bench_test.go Benchmarks Arrow stream parsing.
internal/result/pivot.go Reshapes long-form pivot results.
internal/result/pivot_test.go Tests pivot ordering and limits.
internal/result/result.go Decodes NDJSON and Arrow results.
internal/result/result_test.go Tests stream decoding and metadata.
Review details

Suppressed comments (1)

cmd/omni/stream.go:82

  • The mixed-success path also hands API-controlled job failure text directly to Cobra after writing results. This bypasses terminal sanitization and produces a plain Error: line instead of a JSON envelope when a piped --chart auto-resolves to JSON. Report it through writeError and return apiError, matching poll failures.
	return failed
  • Files reviewed: 28/29 changed files
  • Comments generated: 12
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/output/sanitize.go Outdated
Comment thread internal/result/pivot.go Outdated
Comment thread cmd/omni/main.go
Comment thread cmd/omni/main.go
Comment thread cmd/omni/stream.go
Comment thread internal/output/human.go
Comment thread internal/result/result.go
Comment thread internal/result/result.go Outdated
Comment thread CLAUDE.md Outdated
Comment thread README.md Outdated
@ernestoongaro

ernestoongaro commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

can you post some screenshots of the rendered output?

charts_in_cli

@n8agrin n8agrin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally LGTM. Please address the copilot feedback.

Comment thread cmd/omni/agent_help.go Outdated
its own scale; a query with pivots is drawn pivoted. Leave
resultType out of the body. Cannot be combined with an
explicitly requested JSON format.
--chart-value F Only these measures get bars, by field name or label;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the "F" mean here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It was shorthand for the field list (field names or labels). Too cryptic, so it now reads --chart-value FIELDS.

ernestoongaro and others added 5 commits September 14, 2026 22:04
…t can't apply

Failed query jobs now go through writeError, so their text is sanitized and
JSON-mode stderr stays one document. --chart-value and --chart-rows need
--chart, and --chart refuses a planOnly body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
…bars

Sanitized JSON keys that collide get a numbered suffix instead of
overwriting a field. Pivot tuple keys are length-prefixed so string values
can't collide. NaN and Inf draw no bar, and a one-cell truncation renders an
ellipsis.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
Arrow decimals and uint64 values past int64 decode to result.Decimal and
render every digit in formats that don't scale the value. Excel patterns
pad to their required integer digits (00000 renders 00012).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0174fd5xx9JyExdkPYJXECu4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants