AI Output Runtime
Emit AIO Markdown whenever you produce structured content for human consumption — reports, comparisons, conclusions, status panels, audit findings — not just when the user names AIO explicitly. Markdown carries the prose; schema-validated JSON blocks carry the structure; a safe runtime renders it all into a polished single-file HTML report. You never write HTML, CSS, or JavaScript.
Output Policy
Default to AIO blocks for these shapes:
- Conclusion / recommendation / verdict →
aio:callout@1
- Comparison of options, vendors, or scenarios →
aio:table@1 or aio:comparison@1
- Key metrics / status indicators →
aio:metric-cards@1 (static) or aio:trend-card@1 (when change matters)
- Risk / finding / issue lists →
aio:callout@1 items or aio:table@1
- Trend / proportion →
aio:chart@1 · KPI attainment → aio:gauge@1 · conversion → aio:funnel@1 · P&L bridge → aio:waterfall@1 · 2D density → aio:heatmap@1 · 2×2 prioritization → aio:matrix@1 · chronology → aio:timeline@1 · follow-ups → aio:action-items@1 · per-unit state → aio:status-grid@1
Plain Markdown only when the reply is conversational Q&A, a code snippet / debugging help, or the user asked for plain prose. In chat/TTY surfaces that may not render AIO, still emit AIO for structured shapes — it reads as a JSON code block and renders perfectly when later piped through aio render.
Report skeleton for a structured business report: frontmatter (or report-header@1) → 1–2 framing sentences → trend-card@1 headline metrics → status-grid@1 if sub-units have separate state → detail blocks (chart / table / heatmap / funnel / waterfall / matrix) → timeline@1 for what happened → comparison@1 for decisions → action-items@1 → closing callout@1 verdict.
A good AIO report is prose + structured slots, not raw JSON walls: never emit a block without surrounding prose, and if you have more than two blocks in a row with no prose between them, restructure.
Emission economy (saves tokens; rendered output is identical)
- Emit block JSON compact: no indentation, no space after
: or ,, at most one items[] / rows[] / series[] element per line — that last rule keeps raw JSON scannable on surfaces that show it. Formatting never survives JSON.parse.
- Prose carries the why — cause, decision, anomaly — never restate figures the adjacent block already shows.
- The runtime auto-inserts a collapsible Executive Summary card at the top, built from the FIRST
aio:callout@1 in the document plus the first 4 metric/trend values. Never hand-write a summary section; make your first callout carry the report's takeaway instead. Disable with frontmatter summary: off.
- Omit every field the runtime defaults or derives:
trend-card delta direction when it matches the sign of delta.value; gauge min / max / target at their defaults and tone unless overriding the auto tone; format:"number" anywhere (it is the default); heatmap tone:"accent"; matrix bounds at 0/10; funnel step/overall conversion % (auto-computed — don't put them in note); waterfall kind on start/up/down bars and value on explicit subtotal/end bars. Tones on status-grid / timeline / matrix items are data, not decoration — keep those.
- Pure data tables can use the CSV body (
aio:table@1:csv, ~20–30% fewer bytes than minified JSON) when no title / subtitle / caption / filterable is needed. Prefer the JSON body when the rendering toolchain may predate v0.6.0 — older runtimes degrade :csv silently to a plain code block.
Hard Rules
- Info strings:
aio:name@major (JSON body), plus aio:table@1:csv (CSV body, table only).
- JSON bodies must be valid JSON — no comments, no trailing commas.
- No HTML, CSS, JavaScript, iframes, style attributes, event handlers, template expressions, or custom components. No
< or > inside AIO string fields.
- Components in scope — stable:
table@1 / metric-cards@1 / callout@1; candidate: chart@1 / trend-card@1 / status-grid@1 / report-header@1 / timeline@1 / action-items@1 / comparison@1 / gauge@1 / funnel@1 / waterfall@1 / heatmap@1 / matrix@1. Never invent component names; content that fits nothing (diagrams, code diffs, …) stays plain Markdown.
Components
The stable trio, in exactly the emission style to imitate:
{"title":"Release health","items":[
{"label":"Recommendation","value":"Adopt","note":"Fits stable scope","tone":"good"},
{"label":"Blockers","value":"0","tone":"neutral"}]}
{"tone":"success","title":"Final recommendation","body":"Adopt AIO for structured reports.","items":["Ship v1 next sprint"]}
callout needs title plus body and/or items — body may be omitted when items is non-empty. tone: info / success / warning / danger.
{"title":"Options","columns":["Option","Benefit","Risk"],"rows":[
["Markdown","Readable","Weak interaction"],
["AIO","Validated structure","Requires runtime"]]}
CSV body for pure data tables — header row becomes columns; quote cells containing , or " (escape embedded quotes by doubling); unquoted true / false / number tokens are typed; raw newlines inside cells are forbidden:
Option,Score,Verdict
Markdown,7,Readable
"AIO, validated",9,Recommended
Frontmatter shortcut: a YAML frontmatter block (title / subtitle / period / author / status / data-as-of / classification / badges) at the very top of the document is automatically turned into an aio:report-header@1 — don't also write the block.
Candidate components — key reference
The CLI rejects any key not listed here (? = optional). For edge-case semantics read SKILL_DIR/specs/<name>-v1.md before emitting anything beyond this table.
| Component |
Use for |
Keys |
chart@1 |
line / bar / area / pie / donut |
type, title?, subtitle?, caption?, xLabel?, yLabel?; line/bar/area: x (≤50) + series (≤6) of {name?,data,tone?} where every data.length == x.length; pie/donut: slices (≤12) of {label,value,tone?}, values ≥ 0. Numbers must be finite. No stacked/scatter/log — fall back to a table. Omitted name falls back to the chart title (single series) |
trend-card@1 |
metrics + delta + sparkline |
title?, asOf?, items (≤8) of {label,value,format?,delta?,spark?,tone?,note?}; delta: {value,direction?,format?,label?}; spark: 2–60 finite numbers |
status-grid@1 |
green/yellow/red state per unit |
title?, asOf?, items (≤12) of {label,status,value?,note?}; status: good / warn / bad / neutral / info |
report-header@1 |
formal report hero |
title, subtitle?, period?, author?, status? (draft/review/final/archived), dataAsOf?, classification? (public/internal/confidential/restricted), badges? (≤6) of {label,tone?} (tone: info/good/warn/bad/neutral) |
timeline@1 |
chronological events |
title?, asOf?, items (≤30) of {time,title,body?,tone?}; rendered in source order, never sorted |
action-items@1 |
owned follow-ups |
title?, asOf?, items (≤30) of {task,owner?,due?,status?,priority?}; status: todo/doing/done/blocked; priority: P0–P3; overdue items auto-highlight |
comparison@1 |
options × criteria decision matrix |
title?, subtitle?, asOf?, options (2–6), recommended? (must match one option), criteria (≤14) of {label,values,weight?,tone?}; every values.length == options.length |
gauge@1 |
KPI / OKR attainment arcs |
title?, asOf?, items (≤8) of {label,value,target?,min?,max?,format?,tone?,note?}; defaults: min 0, max 100 (1 when format:"percent"), target = max, tone auto from value/target |
funnel@1 |
sequential conversion |
title?, subtitle?, asOf?, format?, stages (2–8) of {label,value,tone?,note?}; values monotonic non-increasing; step + overall % auto-computed |
waterfall@1 |
P&L bridge / variance / attribution |
title?, subtitle?, asOf?, format?, bars (2–14) of {label,value?,kind?,note?}; omit kind on start/up/down bars (derived from position and sign), omit value only on explicit kind:"subtotal" / "end" bars (derived running total); never omit both |
heatmap@1 |
2D density grid |
title?, subtitle?, asOf?, format?, tone?, xLabels / yLabels (≤32 each), rows of finite numbers matching y×x |
matrix@1 |
2×2 quadrant plot |
title?, subtitle?, asOf?, xLabel?, yLabel?, xMin? / xMax? / yMin? / yMax? (default 0–10), quadrants? (exactly 4), items (≤24) of {label,x,y,tone?,note?} |
format fields accept raw / number / percent / compact / currency:CCY (ISO 4217).
Rendering behavior worth knowing
- Numbers in number-bearing components are locale-formatted via
Intl.NumberFormat from the document lang — with no format, numbers get grouped thousands separators (13,620, not 13620). Pass the right --lang (en / zh-CN / ja) so currency separators and decimal marks render correctly. Use format:"raw" only when you explicitly want no formatting.
- The visual system is business-grade out of the box — 1800px wide-screen shell, phone reflow for every component, sticky TOC, per-block CSV export, strict Light/Dark toggle. Nothing to configure.
- Fenced code blocks get dark chrome, a Copy button, and syntax highlighting automatically:
json / bash / js / ts / python / yaml / diff are built in, 24 more languages (go, rust, java, sql, …) lazy-load from CDN. --inline-runtime artifacts stay offline-friendly — the CLI inlines every language module the markdown actually uses. Unknown languages fall back to plain monospace, never an error.
Local Tools
Validate, then render:
node SKILL_DIR/scripts/aio.mjs validate report.md
node SKILL_DIR/scripts/aio.mjs render report.md --lang zh-CN
By default the rendered HTML references the runtime via jsDelivr CDN, so the file is portable:
<script src="https://cdn.jsdelivr.net/gh/wxkingstar/ai-output-runtime@v0.6.0/assets/ai-output-runtime.js"></script>
Useful render flags:
--inline-runtime — offline / file://-friendly artifact (runtime + used language modules inlined).
--out <file.html> — output path (defaults next to the source).
--theme dark — start in Dark (default is Light).
--expire-hours 72 — sticky countdown banner for artifacts delivered as time-limited download links.
--runtime <url|path> — custom CDN or local runtime override.
Prefer the CDN or --inline-runtime over copying SKILL_DIR/assets/ai-output-runtime.js by hand.
1---2name: ai-output-runtime3description: Use when YOU are producing a structured, scannable artifact for humans rather than a paragraph of prose. Covers weekly / monthly / quarterly / daily reports (周报/月报/季报/日报/汇报/business review), dashboards & status sync (状态同步/盘点/概览/总结/小结/scorecard), postmortems & retros (复盘), code reviews & audits (代码审查/安全审计/审查/审计), KPI / OKR attainment (达成率/进度), financial analysis (财务分析/经营分析/P&L bridge/waterfall/variance), sales funnels & conversion (销售漏斗/转化分析), inventory / logistics / operations / customer / cohort analytics (库存/物流/运营/客户/复购分析), trend & period-over-period analysis (趋势分析/同比/环比/WoW/MoM/YoY), comparisons & selection (对比/方案选型/竞品比较/vendor evaluation/RFP), risk assessment & 2×2 matrices (风险评估/风险矩阵/RICE/BCG/likelihood × impact), heatmaps (热力图), action items & next steps (行动项/待办), and explicit mentions of AIO / AI Output Runtime / aio:name@major — plus validating or rendering AIO Markdown via the CLI. Skip for one-paragraph chat replies, code snippets, debugging help, or when the user explicitly asks for plain prose. Co4---56# AI Output Runtime78Emit AIO Markdown whenever you produce **structured content for human consumption** — reports, comparisons, conclusions, status panels, audit findings — not just when the user names AIO explicitly. Markdown carries the prose; schema-validated JSON blocks carry the structure; a safe runtime renders it all into a polished single-file HTML report. You never write HTML, CSS, or JavaScript.910## Output Policy1112**Default to AIO blocks for these shapes:**1314- Conclusion / recommendation / verdict → `aio:callout@1`15- Comparison of options, vendors, or scenarios → `aio:table@1` or `aio:comparison@1`16- Key metrics / status indicators → `aio:metric-cards@1` (static) or `aio:trend-card@1` (when change matters)17- Risk / finding / issue lists → `aio:callout@1` items or `aio:table@1`18- Trend / proportion → `aio:chart@1` · KPI attainment → `aio:gauge@1` · conversion → `aio:funnel@1` · P&L bridge → `aio:waterfall@1` · 2D density → `aio:heatmap@1` · 2×2 prioritization → `aio:matrix@1` · chronology → `aio:timeline@1` · follow-ups → `aio:action-items@1` · per-unit state → `aio:status-grid@1`1920**Plain Markdown only when** the reply is conversational Q&A, a code snippet / debugging help, or the user asked for plain prose. In chat/TTY surfaces that may not render AIO, still emit AIO for structured shapes — it reads as a JSON code block and renders perfectly when later piped through `aio render`.2122**Report skeleton** for a structured business report: frontmatter (or `report-header@1`) → 1–2 framing sentences → `trend-card@1` headline metrics → `status-grid@1` if sub-units have separate state → detail blocks (`chart` / `table` / `heatmap` / `funnel` / `waterfall` / `matrix`) → `timeline@1` for what happened → `comparison@1` for decisions → `action-items@1` → closing `callout@1` verdict.2324A good AIO report is **prose + structured slots**, not raw JSON walls: never emit a block without surrounding prose, and if you have more than two blocks in a row with no prose between them, restructure.2526### Emission economy (saves tokens; rendered output is identical)2728- **Emit block JSON compact**: no indentation, no space after `:` or `,`, at most one `items[]` / `rows[]` / `series[]` element per line — that last rule keeps raw JSON scannable on surfaces that show it. Formatting never survives `JSON.parse`.29- **Prose carries the why — cause, decision, anomaly — never restate figures the adjacent block already shows.**30- **The runtime auto-inserts a collapsible Executive Summary card** at the top, built from the FIRST `aio:callout@1` in the document plus the first 4 metric/trend values. Never hand-write a summary section; make your first callout carry the report's takeaway instead. Disable with frontmatter `summary: off`.31- **Omit every field the runtime defaults or derives**: `trend-card` delta `direction` when it matches the sign of `delta.value`; `gauge` `min` / `max` / `target` at their defaults and `tone` unless overriding the auto tone; `format:"number"` anywhere (it is the default); `heatmap` `tone:"accent"`; `matrix` bounds at 0/10; funnel step/overall conversion % (auto-computed — don't put them in `note`); `waterfall` `kind` on start/up/down bars and `value` on explicit `subtotal`/`end` bars. Tones on `status-grid` / `timeline` / `matrix` items are data, not decoration — keep those.32- **Pure data tables can use the CSV body** (`aio:table@1:csv`, ~20–30% fewer bytes than minified JSON) when no `title` / `subtitle` / `caption` / `filterable` is needed. Prefer the JSON body when the rendering toolchain may predate v0.6.0 — older runtimes degrade `:csv` silently to a plain code block.3334## Hard Rules3536- Info strings: `aio:name@major` (JSON body), plus `aio:table@1:csv` (CSV body, table only).37- JSON bodies must be valid JSON — no comments, no trailing commas.38- No HTML, CSS, JavaScript, iframes, style attributes, event handlers, template expressions, or custom components. No `<` or `>` inside AIO string fields.39- Components in scope — **stable**: `table@1` / `metric-cards@1` / `callout@1`; **candidate**: `chart@1` / `trend-card@1` / `status-grid@1` / `report-header@1` / `timeline@1` / `action-items@1` / `comparison@1` / `gauge@1` / `funnel@1` / `waterfall@1` / `heatmap@1` / `matrix@1`. Never invent component names; content that fits nothing (diagrams, code diffs, …) stays plain Markdown.4041## Components4243The stable trio, in exactly the emission style to imitate:4445```aio:metric-cards@146{"title":"Release health","items":[47{"label":"Recommendation","value":"Adopt","note":"Fits stable scope","tone":"good"},48{"label":"Blockers","value":"0","tone":"neutral"}]}49```5051```aio:callout@152{"tone":"success","title":"Final recommendation","body":"Adopt AIO for structured reports.","items":["Ship v1 next sprint"]}53```5455`callout` needs `title` plus `body` and/or `items` — `body` may be omitted when `items` is non-empty. `tone`: info / success / warning / danger.5657```aio:table@158{"title":"Options","columns":["Option","Benefit","Risk"],"rows":[59["Markdown","Readable","Weak interaction"],60["AIO","Validated structure","Requires runtime"]]}61```6263CSV body for pure data tables — header row becomes `columns`; quote cells containing `,` or `"` (escape embedded quotes by doubling); unquoted `true` / `false` / number tokens are typed; raw newlines inside cells are forbidden:6465```aio:table@1:csv66Option,Score,Verdict67Markdown,7,Readable68"AIO, validated",9,Recommended69```7071**Frontmatter shortcut**: a YAML frontmatter block (`title` / `subtitle` / `period` / `author` / `status` / `data-as-of` / `classification` / `badges`) at the very top of the document is automatically turned into an `aio:report-header@1` — don't also write the block.7273### Candidate components — key reference7475The CLI **rejects any key not listed here** (`?` = optional). For edge-case semantics read `SKILL_DIR/specs/<name>-v1.md` before emitting anything beyond this table.7677| Component | Use for | Keys |78|---|---|---|79| `chart@1` | line / bar / area / pie / donut | `type`, `title?`, `subtitle?`, `caption?`, `xLabel?`, `yLabel?`; line/bar/area: `x` (≤50) + `series` (≤6) of `{name?,data,tone?}` where every `data.length == x.length`; pie/donut: `slices` (≤12) of `{label,value,tone?}`, values ≥ 0. Numbers must be finite. No stacked/scatter/log — fall back to a table. Omitted `name` falls back to the chart title (single series) |80| `trend-card@1` | metrics + delta + sparkline | `title?`, `asOf?`, `items` (≤8) of `{label,value,format?,delta?,spark?,tone?,note?}`; `delta`: `{value,direction?,format?,label?}`; `spark`: 2–60 finite numbers |81| `status-grid@1` | green/yellow/red state per unit | `title?`, `asOf?`, `items` (≤12) of `{label,status,value?,note?}`; `status`: good / warn / bad / neutral / info |82| `report-header@1` | formal report hero | `title`, `subtitle?`, `period?`, `author?`, `status?` (draft/review/final/archived), `dataAsOf?`, `classification?` (public/internal/confidential/restricted), `badges?` (≤6) of `{label,tone?}` (tone: info/good/warn/bad/neutral) |83| `timeline@1` | chronological events | `title?`, `asOf?`, `items` (≤30) of `{time,title,body?,tone?}`; rendered in source order, never sorted |84| `action-items@1` | owned follow-ups | `title?`, `asOf?`, `items` (≤30) of `{task,owner?,due?,status?,priority?}`; `status`: todo/doing/done/blocked; `priority`: P0–P3; overdue items auto-highlight |85| `comparison@1` | options × criteria decision matrix | `title?`, `subtitle?`, `asOf?`, `options` (2–6), `recommended?` (must match one option), `criteria` (≤14) of `{label,values,weight?,tone?}`; every `values.length == options.length` |86| `gauge@1` | KPI / OKR attainment arcs | `title?`, `asOf?`, `items` (≤8) of `{label,value,target?,min?,max?,format?,tone?,note?}`; defaults: min 0, max 100 (1 when `format:"percent"`), target = max, tone auto from value/target |87| `funnel@1` | sequential conversion | `title?`, `subtitle?`, `asOf?`, `format?`, `stages` (2–8) of `{label,value,tone?,note?}`; values monotonic non-increasing; step + overall % auto-computed |88| `waterfall@1` | P&L bridge / variance / attribution | `title?`, `subtitle?`, `asOf?`, `format?`, `bars` (2–14) of `{label,value?,kind?,note?}`; omit `kind` on start/up/down bars (derived from position and sign), omit `value` only on explicit `kind:"subtotal"` / `"end"` bars (derived running total); never omit both |89| `heatmap@1` | 2D density grid | `title?`, `subtitle?`, `asOf?`, `format?`, `tone?`, `xLabels` / `yLabels` (≤32 each), `rows` of finite numbers matching y×x |90| `matrix@1` | 2×2 quadrant plot | `title?`, `subtitle?`, `asOf?`, `xLabel?`, `yLabel?`, `xMin?` / `xMax?` / `yMin?` / `yMax?` (default 0–10), `quadrants?` (exactly 4), `items` (≤24) of `{label,x,y,tone?,note?}` |9192`format` fields accept `raw` / `number` / `percent` / `compact` / `currency:CCY` (ISO 4217).9394## Rendering behavior worth knowing9596- Numbers in number-bearing components are locale-formatted via `Intl.NumberFormat` from the document `lang` — with no `format`, numbers get grouped thousands separators (`13,620`, not `13620`). Pass the right `--lang` (`en` / `zh-CN` / `ja`) so currency separators and decimal marks render correctly. Use `format:"raw"` only when you explicitly want no formatting.97- The visual system is business-grade out of the box — 1800px wide-screen shell, phone reflow for every component, sticky TOC, per-block CSV export, strict Light/Dark toggle. Nothing to configure.98- Fenced code blocks get dark chrome, a Copy button, and syntax highlighting automatically: `json` / `bash` / `js` / `ts` / `python` / `yaml` / `diff` are built in, 24 more languages (go, rust, java, sql, …) lazy-load from CDN. `--inline-runtime` artifacts stay offline-friendly — the CLI inlines every language module the markdown actually uses. Unknown languages fall back to plain monospace, never an error.99100## Local Tools101102Validate, then render:103104```bash105node SKILL_DIR/scripts/aio.mjs validate report.md106node SKILL_DIR/scripts/aio.mjs render report.md --lang zh-CN107```108109By default the rendered HTML references the runtime via jsDelivr CDN, so the file is portable:110111```html112<script src="https://cdn.jsdelivr.net/gh/wxkingstar/ai-output-runtime@v0.6.0/assets/ai-output-runtime.js"></script>113```114115Useful render flags:116117- `--inline-runtime` — offline / `file://`-friendly artifact (runtime + used language modules inlined).118- `--out <file.html>` — output path (defaults next to the source).119- `--theme dark` — start in Dark (default is Light).120- `--expire-hours 72` — sticky countdown banner for artifacts delivered as time-limited download links.121- `--runtime <url|path>` — custom CDN or local runtime override.122123Prefer the CDN or `--inline-runtime` over copying `SKILL_DIR/assets/ai-output-runtime.js` by hand.