feat(cli): render generated commands as human tables, not raw JSON#46
Merged
Conversation
Generated commands carried no hand-written column set, so in human (table)
mode they fell back to indented JSON. Add a generic, reflection-based
renderer that gives every generated command an aligned table:
- a paginated list envelope ({Items:[...], Total, ...}) or a top-level
row array prints as an aligned table;
- a single object prints as a vertical FIELD/VALUE table;
- anything else falls back to indented JSON, so output is never empty.
Columns come from a display-only seed (display_columns.go) lifted from the
curated commands' hand-written column sets, with a reflective heuristic
(first N scalar/timestamp fields) for unmapped row types. The seed is
display-only: it never affects flags or machine output, so a wrong entry
degrades one table column at worst — it cannot cause a functional error.
Machine modes (--output-format json/toon) are untouched: the renderer is
reached only in table mode; structured output still marshals the full
typed response (verified byte-identical to the direct printer).
This is the keystone for converging the CLI on generated commands as the
single source of truth: human tables no longer require hand-written
commands, so curated shadows can be removed later without losing readable
output. Follow-ups (separate PRs): drop curated shadows group-by-group with
skill-doc migration; unshadow insight to expose its server-side filters;
add team/creator names + incident short-id to the API so the generated
commands inherit them.
ysyneu
added a commit
that referenced
this pull request
Jun 15, 2026
…re-rename shadows, #50 positional args
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Generated commands carried no hand-written column set, so in human (table) mode they fell back to indented JSON. This adds a generic, reflection-based table renderer so every generated command renders an aligned table:
{Items:[...], Total, ...}) or a top-level row array → aligned tableFIELD/VALUEtableColumns come from a display-only seed (
display_columns.go), lifted from the curated commands' hand-written column sets, with a reflective heuristic (first N scalar/timestamp fields) for unmapped row types. The seed is display-only: it never touches flags or machine output, so a wrong entry degrades one column at worst — it can't cause a functional error. A reflection test (TestDisplayColumns_FieldsResolve) fails the build if any seeded field name doesn't resolve on its SDK type.Machine modes (
--output-format json/toon) are untouched — the renderer is reached only in table mode; structured output still marshals the full typed response (test asserts byte-identical to the direct printer).Why
Keystone for converging the CLI on generated commands as the single source of truth: human tables no longer require hand-written commands, so curated "shadow" commands (which freeze a flag subset and hide server-side filters — root cause of the
channel list --team-idsgap) can be removed later without losing readable output.Test plan
go build/vet ./...,go test ./internal/...— green; gofumpt clean.fdutybuilt from this branch):role list(table) → aligned table from real rows (was raw JSON);--output-format json/toon→ unchanged structured.role info(table) → vertical FIELD/VALUE table.e2e/suite (-tags=e2e) → all pass except 4 pre-existingTestStatusPage*failures (unknown command "statuspage"— naming drift on the base branch, unrelated to rendering; confirmed identical onorigin/feat/ai-sre).Follow-ups (separate PRs)
scan.shin lockstep.insight team/channel/responderto expose their server-side filters (severities/*_ids/fields/labels).team_name/creator_nameonchannel list; a short-id (num) param onincident info.