Rich replies
Make a UFO reply easy to read at a glance — show a file with a preview, turn numbers into a chart or status board, put something interactive in the chat, or post a report card.
A UFO reply is markdown, but it can show more than text. Pick by what you want to show:
| You want to show | Do this |
|---|---|
| A file you made — image, video, audio, log, PDF | Write its full path |
| Numbers, a table, a chart, progress, a status board | A ```ui-spec block |
| Something that moves — 3D, a game, an animation, a calculator | A ```ui block |
| A titled report card from a script | ufo send --title |
If plain text says it fine, use plain text.
Show a file
Write the file's full path in the reply — nothing else needed:
Here's the render: /home/me/renders/scene-01.png
Full log: ~/logs/build-812.log
People get a clickable preview:
- Images — inline thumbnail, click to enlarge
- Video and audio — inline player
- Text, code, logs — opens in a viewer
- Anything else — download
For the link to work:
- Keep the file inside the home folder (not
/tmp). - Write the whole path — no
.., no./, no.... - The machine that has the file must be online. To keep a file for good, also save it to Drive.
Show numbers, a table or a chart
Write a ```ui-spec block with JSON and it appears as a screen in the
chat, in UFO's own style.
- Key numbers →
Stat(2–4 in aGrid) - Trend →
LineChart, comparison →BarChart - Many rows →
Table, a warning →Callout, progress →Progress - Add a sentence before or after the block, and keep it to one or two screens per message.
Write a ui-spec code block containing ONE JSON value (an object, or an array that becomes a vertical stack).
Every element is {"type": <Component>, ...props}; props sit directly on the element, not inside "props". Props marked * are required.
Only these components exist; anything else shows a red error box. No HTML, no styles, no URLs, no code runs.
Limits: 300 elements, 6 levels deep, 100000 characters.
- Stack — Lays children out in a row or a column. Props: direction: "column" | "row" (default column); gap: "sm" | "md" | "lg" (default md); children: component[].
- Grid — Equal-width columns; wraps on narrow screens. Props: columns: number 1-4 (default 2); children: component[].
- Card — A bordered box with an optional title. Props: title: string; children: component[].
- Heading — A heading. Props: text*: string; level: number 1-3 (default 2).
- Text — A paragraph of text. Props: text*: string; tone: "normal" | "muted" | "success" | "warning" | "danger".
- Callout — A highlighted note. Props: text*: string; title: string; tone: "info" | "success" | "warning" | "danger".
- Stat — One big number with a label (KPI tile). Props: label*: string; value*: string | number; delta: string | number (small change text, e.g. "+12%"); tone: "neutral" | "success" | "warning" | "danger".
- Badge — A small status pill. Props: text*: string; tone: "neutral" | "success" | "warning" | "danger".
- Progress — A progress bar, value 0-100. Props: value*: number 0-100; label: string.
- List — A bulleted or numbered list. Props: items*: string[] (max 100); ordered: true | false.
- KeyValue — Label/value pairs, one per line. Props: items*: [{"key": string, "value": string}] (max 50).
- Table — A table. rows is an array of rows; each row is an array of cells (same order as columns). Props: columns*: string[] (max 100); rows*: string|number rows, array of arrays (max 200 rows x 12 cols).
- BarChart — Vertical bars. data: [{label, value}]. Negative values are fine. Props: data*: [{"label": string, "value": number}] (1-100); title: string; unit: string (suffix like "%" or "ms").
- LineChart — A single line over ordered points. data: [{label, value}]. Props: data*: [{"label": string, "value": number}] (1-100); title: string; unit: string (suffix like "%" or "ms").
- Code — A monospace code/log block (plain text, not run). Props: text*: string.
- Divider — A thin horizontal line.
Example:
{
"type": "Card",
"title": "Deploy status",
"children": [
{
"type": "Grid",
"columns": 3,
"children": [
{
"type": "Stat",
"label": "Tests",
"value": 1352,
"delta": "all pass",
"tone": "success"
},
{
"type": "Stat",
"label": "Build",
"value": "1m 25s"
},
{
"type": "Badge",
"text": "live",
"tone": "success"
}
]
},
{
"type": "BarChart",
"title": "Build time (s)",
"data": [
{
"label": "Mon",
"value": 90
},
{
"label": "Tue",
"value": 85
}
]
}
]
}
If the JSON has a typo, people see the raw code with a red error line. A wrong component or prop shows a small red box in its place.
Show something interactive
Write a ```ui block with HTML, CSS and JavaScript. Use it for what a
chart can't do: 3D, games, animation, calculators, drawing.
<canvas id="c" width="320" height="120"></canvas>
<script>
const g = document.getElementById('c').getContext('2d'); let t = 0
;(function f() { g.clearRect(0, 0, 320, 120); g.fillStyle = '#7c9cff'
g.beginPath(); g.arc(160 + Math.sin(t) * 120, 60, 16, 0, 7); g.fill(); t += 0.04; requestAnimationFrame(f) })()
</script>
What works and what doesn't:
| Works | Doesn't |
|---|---|
One HTML piece up to 200 KB, with <style> and <script> |
Loading data from the internet (fetch, images by URL) |
Libraries from cdn.jsdelivr.net or cdnjs.cloudflare.com (three.js, Chart.js, d3…) |
eval, storage, cookies, pop-ups, leaving the page |
| Google Fonts | Other script sites |
Images as data: URLs |
Put the data right in the code. The background is dark (#14141a) with light
text, so check your colors. It starts when scrolled into view and grows up to
640 px tall.
Post a report card
From a script or a shell:
ufo send releases "Shipped to production" --title "Release 1.4" --color green -f "Commit=a1b2c3d"
See the ufo command line.