feat(cli): render human output through a shared formatter kit - #4686
Open
josephfarina wants to merge 15 commits into
Open
feat(cli): render human output through a shared formatter kit#4686josephfarina wants to merge 15 commits into
josephfarina wants to merge 15 commits into
Conversation
JSON stays the source of truth; the plain-text output is now a consistent, greppable projection of it. Adds clients/cli/formatters with an opaque Block type and a small renderer set — emit (the single stdout sink), title, section, text, list, record/records (JSON object -> aligned key: value), table, code, markdown. Output is deterministic plain ASCII (no color, no TTY, no width detection); errors/warnings stay on stderr via cliError. Migrates every command's human branch onto emit + renderers (search, build, component, docs, hook, template, discover, doctor, swizzle, layout, theme, validate-integration, blog). Enforces the funnel by extending @astryx/no-raw-console-cli to ban humanLog/humanWarn in command files (emit is now the only path); humanLog stays internal to the logger and formatters. JSON envelopes are unchanged. CLI test suite (895 tests) passes. Co-authored-by: Cursor <cursoragent@cursor.com>
josephfarina
requested review from
cixzhang,
ejhammond and
imdreamrunner
as code owners
August 3, 2026 19:14
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…order) Replace the status-like "Building \"<q>\"" header with a titled "Build kit for \"<q>\"" plus a short legend: what the output is, how to use it (run START, then pull from the sections; each item has a `command:`), and the exact order of the sections that follow (computed from the ones actually present) so it reads clearly and parses predictably. Co-authored-by: Cursor <cursoragent@cursor.com>
Rename the build kit's "START" section to "RECOMMENDED START" and explain why:
the closest page template is called out as the recommendation ("this template
appears closest ... we recommend starting here"), with an explicit fallback
("otherwise browse PAGE TEMPLATES, then BLOCKS and DOMAIN COMPONENTS"). Keeps the
top legend in sync.
Co-authored-by: Cursor <cursoragent@cursor.com>
The recommended scaffold command hardcoded `./src/App.tsx`, which wrongly assumes the consumer's project layout. Use a `<path>` placeholder instead and explain in the note that it's the file (or folder) to write the template to. The non-direct-match case now points at the printed `--skeleton` layout as the reference rather than implying a scaffold path. Co-authored-by: Cursor <cursoragent@cursor.com>
Human output is meant to be plain ASCII, but data (e.g. template display names like "Avatar — Group") and prose carried em/en dashes, curly quotes, and ellipses. The prose/data renderers (title, section, text, list, record, table) now normalize these to ASCII (— -> -, curly quotes -> straight, … -> ...); the verbatim renderers (code, markdown) and --json are untouched. Table cells are normalized before width calc so alignment stays correct. Co-authored-by: Cursor <cursoragent@cursor.com>
Commander wraps each help description to a column width (80 when captured non-TTY), splitting long descriptions like --json across several indented lines. Override Help.wrap to a no-op via configureHelp so every option/command renders on a single line — deterministic and width-independent, matching the rest of the CLI. Set before subcommands register so they inherit it (copyInheritedSettings). Co-authored-by: Cursor <cursoragent@cursor.com>
The names view interleaved headerless standalone components with "(group)" family sections and ragged blank lines, which read as choppy/weird. Render a single sorted table of Component + Import instead (the import column already conveys families); package-qualify external/colliding names. Also drop the "(group)" suffix from the --detail compact headers. Co-authored-by: Cursor <cursoragent@cursor.com>
The manifest summary used a nested 2/4-space indent (command on one line, description indented under it) plus a raw em-dash, which was hard to grep and inconsistent. Render it through the formatter as a single Command | JSON | Description table instead (one row per command, ASCII-normalized). Co-authored-by: Cursor <cursoragent@cursor.com>
Add an "Output format" section to `astryx --help` describing the plain-text contract so agents can parse/grep it reliably: records are blocks of aligned "key: value" lines separated by a single blank line (RFC-822 / recfile style), sections are a header line + optional subtitle, and --json is the stable structured surface. Formalizes the convention rather than adding per-item divider lines (a blank line is the standard record separator). Co-authored-by: Cursor <cursoragent@cursor.com>
Consolidate the formatter kit to one small, formalized set and document it in --help so every current and future command conforms and agents can rely on it: - Data is always records (key: value blocks, blank-line separated) — dropped `table`; `component --list` and `manifest` are now records too. - Merged `title` into `section` (a heading with no subtitle) and `markdown` into `code` (one verbatim block: source, skeleton, or doc). - Final vocabulary: section, record/records, list, text, code, emit. - `astryx --help` now documents the block types (Record/Section/List/Text/Code), the blank-line record separator, how to grep a field, and that --json is the structured surface. Net -75 lines. All CLI tests pass; tsc strict + lint clean. Co-authored-by: Cursor <cursoragent@cursor.com>
Render the "Output format" help through the formatter itself — a section heading, one record per block type (block/shape), and a text footer — so the spec is written in the very format it documents (records separated by blank lines). Co-authored-by: Cursor <cursoragent@cursor.com>
@astryxdesign/cli patch — human output now renders through the documented formatter kit; --json unchanged. Co-authored-by: Cursor <cursoragent@cursor.com>
The blog detail body was rendered via text(), which ASCII-normalizes content (em/en dashes, curly quotes, ellipses) — silently altering the article. Use code() so the post body is emitted byte-for-byte, and render the feed URL as a record. Caught by Bugbot. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # packages/cli/clients/cli/commands/build.mjs # packages/cli/clients/cli/commands/layout.mjs # packages/cli/clients/cli/commands/search.mjs
Post-merge, main's new `--verbose` regression test asserted the old combined `match:` line. The formatter renders ranking detail as separate `score:` / `reason:` record fields (mirroring --json), so assert those instead. Co-authored-by: Cursor <cursoragent@cursor.com>
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.
Summary
--jsonstays the source of truth; the human (non---json) output is now a consistent, greppable plain-ASCII projection of that same JSON, rendered through a newpackages/cli/clients/cli/formatterskit.emit(the single stdout sink),record/records(a JSON object/array -> alignedkey: value), plustitle,section(with optional subtitle),text,list,table,code,markdown. Output is deterministic — no color, no TTY detection, no width wrapping — so it's identical piped or not.Blockandemit(...)accepts onlyBlocks (a@ts-expect-errorguard provesemit('string')won't compile undercheckJs/strict).emit+ renderers:search,build,component,docs,hook,template,discover,doctor,swizzle,layout,theme,validate-integration,blog.upgrade/initalready stream progress via the sharedloggerand are unchanged.@astryx/no-raw-console-clito banhumanLog/humanWarnin command files, soemitis the only stdout path (errors/warnings stay on stderr viacliError).humanLogremains internal to the logger + formatters.Notes / follow-ups
--jsonenvelopes are byte-for-byte unchanged.→,•,✓/⚠/✗) were replaced with ASCII (->,-,[ok]/[warn]/[fail]); em-dashes that come from data (component/template descriptions) pass through verbatim.@astryxdesign/clipatch changeset still needs to be added (left out to avoid guessing the author handle).api/buildcandidate selection (it surfaces several low-relevance blocks), and optionally migrate themanifest/postinstallbanner inclients/cli/index.mjs.Test plan
pnpm exec vitest run packages/cli/clients— 895 tests passpnpm -F @astryxdesign/cli typecheck:strict— clean (only the pre-existingtheme build --checktype errors remain)pnpm exec eslint packages/cli/{clients,api,foundation}— clean; newhumanLog/humanWarnban verified with a probeastryx search button,astryx build "user settings page",astryx doctorMade with Cursor