@khanglvm/relay 0.12.3 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -87,6 +87,7 @@ Node ≥ 18; Chart.js / Mermaid / Graphviz are vendored and lazy-loaded offline.
87
87
  | | |
88
88
  |---|---|
89
89
  | `rly help` | every command at a glance |
90
+ | `rly view <file.md> …` | open a quick read-only board that renders local markdown file(s) — library-free; great for plans, READMEs, reports |
90
91
  | `rly install --target <agent>` | write relay's rules into an agent's instruction file — `claude` `codex` `cursor` `copilot` `kiro` `windsurf` `cline` `gemini` `opencode` `droid` `agents`; `--all`, `--scope`, `--print`, `--list` |
91
92
  | `rly upgrade` | update the CLI **and** refresh the skill in one step (safe around open boards; `--dry-run`, `--cli-only`, `--skill-only`) |
92
93
  | `rly mcp` | run relay as an MCP App server so boards render **inline** in the chat — **stdio** for local desktop hosts (Claude Desktop, Codex), or `rly mcp --http` (Streamable HTTP) for web/mobile/remote; `rly mcp config` / `rly mcp install --target claude\|codex` to register it |
package/docs/AGENT.md CHANGED
@@ -5,6 +5,15 @@ rich content blocks (markdown, charts, diagrams, tables, code, custom HTML),
5
5
  then **wait for them to click Submit** and read the answers as JSON from stdout.
6
6
  No "type 'done' in the terminal", no hand-rolled HTML+server.
7
7
 
8
+ **Match the surface to the content.** You run inside a terminal, an IDE side-panel
9
+ (VS Code / JetBrains / Cursor), or a plain-text chat — none of which can render a
10
+ markdown file, an image, a chart, a table, a diagram, or a diff. They degrade to
11
+ walls of monospace text, or can't show it at all (a terminal can't draw a PNG).
12
+ relay renders it in a real browser board. So whenever your output would read better
13
+ than monospace — or is a file/visual the user should look at — show it in relay
14
+ instead of printing it. Fast paths: `rly view file.md` (or `data.csv`), `rly diff`,
15
+ or an `image`/`chart`/`table` block on a board.
16
+
8
17
  **Use relay even in plan mode** — for any clarifying or decision question (Claude
9
18
  Code / Codex), route it through a relay board, **not** the native
10
19
  `AskUserQuestion` / ask-user tool: that tool is **not** a substitute, even though
@@ -135,6 +144,21 @@ Visualization-only (no questions; submit button reads "Acknowledge"):
135
144
  rly show --html-file prototype.html --title "Dashboard concept" --height 600
136
145
  ```
137
146
 
147
+ Read a markdown file (no questions; library-free renderer; submit reads "Done"):
148
+
149
+ ```sh
150
+ rly view PLAN.md # one file
151
+ rly view README.md CHANGELOG.md # several, each under a filename heading
152
+ rly view data.csv # .csv/.tsv/.json → a sortable, filterable table
153
+ ```
154
+
155
+ Show a git diff in one step (sugar — runs git diff, opens a diff board):
156
+
157
+ ```sh
158
+ rly diff --detach # working-tree diff (git args pass through:
159
+ rly diff --staged --split # --staged, HEAD~1, -- path, …); --split = side-by-side
160
+ ```
161
+
138
162
  ## Board spec (JSON)
139
163
 
140
164
  ```jsonc
@@ -188,11 +212,27 @@ rly show --html-file prototype.html --title "Dashboard concept" --height 600
188
212
  | `text` | `"string"` |
189
213
  | `textarea` | `"string"` |
190
214
  | `scale` | number (`min`…`max`, default 1–5) |
191
- | `color` | hex string (e.g. `"#c2674b"`) — native picker + hex field; optional `presets:["#…"]` swatches |
215
+ | `color` | color string (e.g. `"#c2674b"`, `"rgb(...)"`, a name) — native picker + hex field; optional `presets:["#…"]` small swatches, or a `palette:[…]` of labeled swatch CARDS (click = answer, each card commentable, any CSS color system) |
216
+ | `rank` | `["b","a","c"]` — ordered option values, highest priority first |
217
+ | `checklist`| `{ "login":"pass", "checkout":"fail" }` — per-item status map |
218
+ | `allocate` | `{ "eng":50, "design":30, "ops":20 }` — budget distributed across options |
219
+
220
+ `rank` renders the `options` as a reorderable list (drag or ↑/↓ buttons); the
221
+ user prioritizes them and the answer is the ordered array of values. Needs ≥2
222
+ options, takes no "Other", and **always returns a value** (an untouched rank
223
+ submits the authored order), so it never appears in `skipped`. Reach for it on
224
+ roadmap / feature-prioritization questions instead of forcing a single pick.
225
+
226
+ `checklist` gives each `option` a status control (default Pass / Fail / N·A;
227
+ override with `"statuses"`); answer is `{optionValue: statusValue}`. `allocate`
228
+ distributes a budget (`"total"`, default 100) across the options with sliders +
229
+ a live total bar; answer is `{optionValue: number}`. Both take per-option
230
+ `description`/`blocks` like single/multi.
192
231
 
193
232
  Aliases accepted: radio/choice/select→single, checkbox→multi,
194
233
  boolean/bool/yn→yesno, input→text, longtext→textarea, rating/likert→scale,
195
- colour/swatch→color.
234
+ colour/swatch→color, ranking/order/ordering/prioritize/sort→rank,
235
+ signoff/qa→checklist, budget/distribute/points→allocate.
196
236
 
197
237
  ### Result JSON (stdout)
198
238
 
@@ -279,11 +319,14 @@ Rules of thumb:
279
319
 
280
320
  ```jsonc
281
321
  // Markdown — built-in mini renderer, no library. Headings, lists, code, quotes,
282
- // links, and GFM pipe tables all render. For real tabular DATA use a `table`
283
- // block instead (sortable + per-cell comments); markdown tables are display-only.
322
+ // links, images, and GFM pipe tables all render. For real tabular DATA use a
323
+ // `table` block instead (sortable + per-cell comments); markdown tables are display-only.
284
324
  { "type": "markdown", "md": "## Heading\nAny **CommonMark** prose." }
325
+ // Load the body from a local .md file instead of inlining it (README, plan, report).
326
+ // Quick view of whole file(s) with no questions: `rly view file.md [more.md …]`.
327
+ { "type": "markdown", "mdFile": "README.md" }
285
328
 
286
- // Mermaid diagram — vendored, lazy-loaded; natural height, max 1200 px + scroll
329
+ // Mermaid diagram — vendored, lazy-loaded; natural height, max 800 px + scroll (set height: to override)
287
330
  { "type": "mermaid", "code": "graph TD; A-->B; B-->C", "height": 400 }
288
331
 
289
332
  // Graphviz diagram — vendored viz-standalone.js (Graphviz-WASM), fully offline
@@ -397,6 +440,23 @@ real path over telling the user to paste it into a terminal.
397
440
 
398
441
  ### When to use which block
399
442
 
443
+ **Reach for the most specific component — never default to prose.** relay has a
444
+ purpose-built block/question type for most content; falling back to a `markdown`
445
+ block (or terminal text) when a dedicated one fits is the #1 way agents
446
+ under-use relay. A metric → `kpi`, a before/after → `compare`, a priority call →
447
+ a `rank` question. When unsure what exists, consult this table, `rly schema`, and
448
+ the question-types section above.
449
+
450
+ **By domain** (serve the reader the right shape):
451
+ - **Business / PM / exec** — `kpi` + `chart` + `table` for numbers; `rank`
452
+ (prioritize), `allocate` (split a budget), `checklist` (sign-off), `scale` (confidence).
453
+ - **Designer** — `image` (+`pins`), `compare` (before/after), `palette`, `typography`;
454
+ a visual inside each option.
455
+ - **Engineer** — `diff` (`rly diff`), `code` (line-comments), `mermaid`/`graphviz`
456
+ (architecture; editable mermaid to co-design).
457
+ - **Data / analyst** — `table` with `rowsFile`/`filterable`/`exportable`
458
+ (`rly view data.csv`), `chart`.
459
+
400
460
  | Block | Best for |
401
461
  |---|---|
402
462
  | `mermaid` | flows, state machines, architecture overviews, sequence diagrams |
@@ -404,14 +464,23 @@ real path over telling the user to paste it into a terminal.
404
464
  | `plantuml` | UML diagrams (sequence, class, component) via server rendering; great for detailed interface contracts |
405
465
  | `chart` | numbers, trends, comparisons, metrics |
406
466
  | `table` | structured comparisons, option matrices, data grids — **use this for any tabular data**: it's sortable and every cell is commentable, unlike a markdown pipe table |
407
- | `markdown` | prose context, background, instructions, section headings (renders GFM pipe tables too, but reach for a `table` block for real data) |
467
+ | `markdown` | prose context, background, instructions, section headings (renders GFM pipe tables + images too, but reach for a `table` block for real data); load a whole `.md` file with `mdFile` — or `rly view file.md` for a quick read-only board |
408
468
  | `code` | code snippets, config examples, command output — syntax-highlighted + line-numbered; load from a file with `codeFile` |
409
469
  | `diff` | proposed code changes / before-after — a unified diff rendered as a colored git-style comparison (no git needed) |
410
470
  | `video` | demos, screen recordings, walkthroughs — YouTube/Vimeo embeds, a media URL, or a local video file (streamed) |
411
471
  | `image` | screenshots, mockup exports, photos — local files embed and work offline |
412
472
  | `palette` | color palettes / themes — swatch cards with hover-hex + click-to-copy; pair with a `color` question to let the user pick |
473
+ | `kpi` | big-number metric cards (`items:[{label,value,delta?,dir?,sub?}]`) with up/down/flat-tinted deltas — at-a-glance numbers without a chart |
474
+ | `typography` | type specimens (`specimens:[{label?,size?,weight?,font?,text?}]`) — react to type choices like a palette |
475
+ | `compare` | before/after images with a draggable divider (`before`/`after` = url/path/`{src,label}`) — redesigns, before-after fixes |
413
476
  | `html` | anything else — pixel-perfect mockups, custom widgets, embeds |
414
477
 
478
+ Two cross-block fields work on **any** block: `"ref":"name"` makes it openable in
479
+ a modal via a markdown `[label](#ref:name)` link (point a question at a visual
480
+ shown earlier — no scrolling); `image` blocks also take `"pins":true` for
481
+ click-to-drop coordinate pin-comments. `table` blocks take `"rowsFile"` (load
482
+ rows from .csv/.tsv/.json), `"filterable"`, and `"exportable"` (CSV download).
483
+
415
484
  ### Height rules
416
485
 
417
486
  - `markdown`, `code`: natural flow (no fixed height).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanglvm/relay",
3
- "version": "0.12.3",
3
+ "version": "0.13.0",
4
4
  "description": "Question boards with rich blocks (markdown, charts, mermaid, tables, code, diffs, video, sandboxed HTML), clickable local file-links, and element-level annotations for AI coding agents (Claude Code, Codex, …): ask users structured questions, present interactive visuals, collect inline comments, read answers as JSON — in a local browser board OR rendered INLINE inside the Claude & Codex apps as an MCP App (SEP-1865).",
5
5
  "keywords": [
6
6
  "ai-agents",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: relay
3
- description: "The tool for collecting user requirements, decisions, and answers (choice, yes-no, text, scale) and for presenting prototypes, plans, structures, code changes, or reports with rich visuals - mermaid/graphviz/plantuml diagrams, charts, tables, code, diffs, video, custom HTML, clickable file-links - plus inline comments on any element. Opens a browser board, waits for Submit, returns JSON answers, per-question notes, comments, and annotations. Use PROACTIVELY instead of (a) native ask-user tools for 2+ answers or options needing explanation, (b) ASCII trees/tables/diagrams in the terminal or prose for structures/designs/plans, (c) hand-rolled HTML demos. Triggers: collect requirements, ask the user, get decisions/feedback, present a prototype, plan/design review, show me the structure/file tree, architecture or dependency graph, visualize, diagram, chart, table, compare alternatives, survey, edit the diagram, show me the diff / git diff, video walkthrough, open a file. Skip for a single yes/no confirmation."
3
+ description: "Collect user decisions & answers choice, multi, yes-no, text, scale, rank (prioritize), checklist (sign-off), allocate (split a budget), color — and present plans, data, code, or designs with blocks: diagrams (mermaid/graphviz/plantuml), charts, KPI cards, tables (sortable/filterable/CSV, load .csv/.json), code, multi-file diffs, before/after compare, typography, palettes, video, images (click-to-pin), HTML — plus inline element comments and links that open a visual in a modal. Opens a browser board (or inline in Claude/Codex), returns JSON answers/notes/comments/annotations on Submit. Use PROACTIVELY over native ask tools (2+ answers), ASCII/prose for structures/metrics/designs, or printing a file/image/chart/diff the terminal can't render. A dedicated component exists for most content use the most specific, don't fall back to prose. Triggers: ask the user, decisions, prioritize, sign-off, allocate, plan review, chart, metrics, table, view a CSV, before/after, git diff, markdown file. Skip single yes/no."
4
4
  ---
5
5
 
6
6
  # relay (`rly`)
@@ -25,9 +25,19 @@ Schema).** The essentials are below.
25
25
 
26
26
  ## When to use rly vs your native question tool
27
27
 
28
+ **The core principle — match the surface to the content.** You run inside a
29
+ terminal, an IDE side-panel (VS Code / JetBrains / Cursor), or a plain-text chat.
30
+ None of those can actually render a markdown file, an image, a chart, a table, a
31
+ diagram, or a diff — they degrade to walls of monospace text, or can't show it at
32
+ all (a terminal can't draw a PNG). relay opens a real browser board that renders it
33
+ properly and reads the user's reply back as JSON. **So whenever what you're about to
34
+ output would read better than monospace — or is a file/visual the user should look
35
+ at — show it in a relay board instead of printing it.**
36
+
28
37
  | Situation | Use |
29
38
  |---|---|
30
39
  | One trivial confirmation ("proceed?") | native tool |
40
+ | Show an image / screenshot / mockup / rendered output | **rly** (`image` block — the terminal can't display pixels) |
31
41
  | 2+ questions, or options that need descriptions | **rly** |
32
42
  | Choice is easier to make visually (layouts, designs, diagrams) | **rly** (blocks per question) |
33
43
  | Each OPTION has its own visual (design variants, screenshots, charts) | **rly** (blocks per option) |
@@ -38,6 +48,7 @@ Schema).** The essentials are below.
38
48
  | "Show me the diff" / git diff / code changes / before-after | **rly** (`diff` block — run `git diff`, render it; never dump it in the terminal) |
39
49
  | A demo, screen recording or walkthrough | **rly** (`video` block) |
40
50
  | Point the user at a file to open (log, capture, report) | **rly** (a clickable local file-link in markdown) |
51
+ | Let the user read a markdown file (README, plan, report) | **rly view file.md** (or a `markdown` block with `mdFile`) — never dump the file into the terminal |
41
52
  | Plan-mode clarifying question (Claude Code / Codex) | **rly** (not AskUserQuestion / the native ask tool) |
42
53
  | Something you can decide yourself from context | neither — just decide |
43
54
 
@@ -51,6 +62,64 @@ Once the user has answered one board in a session, prefer boards for later
51
62
  question rounds too — they've shown they engage with them. Batch related
52
63
  questions into ONE board rather than opening several in a row.
53
64
 
65
+ ## The full toolbox — reach for the MOST SPECIFIC component
66
+
67
+ relay ships a purpose-built component for most kinds of content. **Before you
68
+ build a board, scan this list and pick the most specific component that fits — do
69
+ NOT fall back to a plain `markdown`/prose block (or the terminal) when a dedicated
70
+ one renders it better.** A KPI belongs in `kpi`, a before/after in `compare`, a
71
+ priority call in a `rank` question — not in paragraphs. Unsure which exists? Run
72
+ `rly agent` (full guide) and `rly schema` (every field).
73
+
74
+ **Blocks** — add under `"blocks": [...]` at the board, a question, or an option:
75
+
76
+ | Block | Reach for it when you have… |
77
+ |---|---|
78
+ | `table` | tabular data — sortable, per-cell comments; `rowsFile` (.csv/.json), `filterable`, `exportable` |
79
+ | `chart` | numbers / trends / comparisons (bar·line·pie·doughnut·radar·scatter) |
80
+ | `kpi` | headline metrics — big-number cards with ↑/↓/flat deltas (no chart needed) |
81
+ | `mermaid` | flows, sequences, state machines, architecture (set `editable:true` to co-edit) |
82
+ | `graphviz` | precise dependency / call graphs |
83
+ | `plantuml` | UML (sequence / class / component) |
84
+ | `code` | source / config / command output — highlighted, line numbers, hover-a-line to comment |
85
+ | `diff` | code changes — colored unified/split, multi-file (`rly diff` builds the whole board) |
86
+ | `image` | screenshots / mockups / renders — zoom+pan; `pins:true` → click-to-drop point comments |
87
+ | `compare` | a before/after pair — draggable divider |
88
+ | `video` | a demo / screen recording / walkthrough |
89
+ | `palette` | color schemes — swatch cards, hover-hex, click-to-copy |
90
+ | `typography` | type choices — specimens at given size/weight/font |
91
+ | `html` | anything bespoke — custom widgets, pixel-perfect mockups |
92
+ | `markdown` | prose / context ONLY (not data, metrics, or visuals — those have their own block) |
93
+
94
+ Any block also takes `"ref":"name"` → a question can link to it with
95
+ `[label](#ref:name)` and it opens **in a full-screen modal**, so the user views the
96
+ data without scrolling back up.
97
+
98
+ **Question types** — pick by the shape of the answer you need:
99
+
100
+ | Type | Reach for it when you need… | Answer JSON |
101
+ |---|---|---|
102
+ | `single` | one choice (radio; "Other" + a note are on by default) | `"value"` |
103
+ | `multi` | several choices | `["a","b"]` |
104
+ | `rank` | a **priority order** over options (roadmap, triage) | `["b","a","c"]` |
105
+ | `allocate` | a **budget split** across options (tradeoffs, %, points) | `{opt: number}` |
106
+ | `checklist` | **per-item sign-off / QA** (Pass·Fail·N·A, or custom) | `{opt: status}` |
107
+ | `scale` | a rating on a 1–N scale | number |
108
+ | `yesno` | a binary decision | `"yes"`/`"no"` |
109
+ | `color` | a color pick — native picker + presets, or a `palette` of labeled swatch cards (each commentable); any CSS color system | color string |
110
+ | `text` / `textarea` | short / long free text | string |
111
+
112
+ **By who you're serving** (don't make a business user read a wall of prose):
113
+
114
+ - **Business / PM / exec** → `kpi` + `chart` + `table` for the numbers; `rank` to
115
+ prioritize, `allocate` for tradeoffs, `checklist` for sign-off, `scale` for confidence.
116
+ - **Designer** → `image` (+`pins` for point feedback), `compare` (before/after),
117
+ `palette`, `typography`; put a visual INSIDE each option so they pick by looking.
118
+ - **Engineer** → `diff` (`rly diff`), `code` (line-comments), `mermaid`/`graphviz`
119
+ for architecture, editable mermaid to co-design.
120
+ - **Data / analyst** → `table` with `rowsFile`/`filterable`/`exportable`
121
+ (`rly view data.csv`), `chart` for the shape of it.
122
+
54
123
  ## Choose a pattern
55
124
 
56
125
  **DEFAULT: detached.** Most agent shell tools kill long-running commands, and a
@@ -105,10 +174,49 @@ answers and any annotations written so far.
105
174
  ```
106
175
 
107
176
  Types: `single`, `multi`, `yesno`, `text`, `textarea`, `scale`, `color`
108
- (native picker + hex; optional `"presets":["#…"]`, returns a hex string). Users
109
- may submit with unanswered questions (returned in `skipped`) unless
177
+ (native picker + hex; optional `"presets":["#…"]` or a `"palette"` of labeled,
178
+ commentable swatch cards any CSS color system), `rank`, `checklist`, `allocate`.
179
+ Users may submit with unanswered questions (returned in `skipped`) unless
110
180
  `"allowPartial": false` or per-question `"required": true`.
111
181
 
182
+ ```jsonc
183
+ { "id": "brand", "type": "color", "label": "Pick a brand color",
184
+ "palette": [{ "value": "#c2674b", "label": "Terracotta" }, { "value": "rgb(77,138,102)", "label": "Forest" }, "rebeccapurple"] }
185
+ // clicking a swatch = the answer; hover a swatch to comment on that specific color.
186
+ ```
187
+
188
+ The optional per-question note box (`result.notes[id]`) defaults ON for the
189
+ **decision types** — `single`, `rank`, `checklist`, `allocate` — so the user can
190
+ qualify a pick; `"note": false` hides it, `"note": true` adds it to other types.
191
+
192
+ `rank` — the user drags (or uses ↑/↓) to order the `options` by priority; the
193
+ answer is the **ordered array of option values**, highest first. Needs ≥2
194
+ options; always returns a value (an untouched rank submits the authored order),
195
+ so it's never `skipped`. Use for roadmap/feature prioritization instead of a
196
+ single pick. Options take `description` and per-option `blocks` like single/multi.
197
+
198
+ ```json
199
+ { "id": "roadmap", "type": "rank", "label": "Order these by priority",
200
+ "options": [{ "value": "diff", "label": "rly diff", "description": "git diff → board" }, "rank type", "image pins"] }
201
+ ```
202
+
203
+ `checklist` — each `option` gets a per-item status (default **Pass / Fail / N·A**;
204
+ override with `"statuses"`). Answer is a map `{optionValue: statusValue}`. For QA
205
+ passes and sign-off gates.
206
+
207
+ ```json
208
+ { "id": "qa", "type": "checklist", "label": "Release sign-off", "options": ["login","search","checkout"] }
209
+ ```
210
+
211
+ `allocate` — the user distributes a budget (`"total"`, default 100) across the
212
+ `options` with sliders + a live running-total bar. Answer is a map
213
+ `{optionValue: number}`. Captures intensity/tradeoffs, not just a pick.
214
+
215
+ ```json
216
+ { "id": "spend", "type": "allocate", "label": "Split the quarter", "total": 100, "unit": "pts",
217
+ "options": ["features","tech debt","infra"] }
218
+ ```
219
+
112
220
  Set `"note": true` on a question to add a small optional free-text field under
113
221
  it — use when the user may want to qualify their choice. Returned as
114
222
  `result.notes[questionId]`. `single` (radio) questions include this note by
@@ -127,6 +235,7 @@ single/multi question.
127
235
 
128
236
  ```jsonc
129
237
  { "type": "markdown", "md": "## Section\n**prose**" }
238
+ { "type": "markdown", "mdFile": "README.md" } // render a local .md file (no lib)
130
239
  { "type": "mermaid", "code": "graph TD; A-->B", "height": 400 }
131
240
  { "type": "graphviz", "dot": "digraph { a -> b }", "height": 300 }
132
241
  { "type": "plantuml", "code": "@startuml\nA->B\n@enduml", "height": 300 }
@@ -137,6 +246,19 @@ single/multi question.
137
246
  { "type": "table", "columns": ["A","B"], "rows": [["x","y"]], "sortable": true }
138
247
  // ^ use a `table` block for tabular data — sortable + per-cell comments.
139
248
  // (markdown blocks render GFM pipe tables too, but those are display-only.)
249
+ { "type": "table", "rowsFile": "data.csv", "filterable": true, "exportable": true }
250
+ // ^ load rows from a local .csv/.tsv/.json; filterable = live filter box,
251
+ // exportable = CSV download. (`rly view data.csv` does all of this for you.)
252
+ { "type": "kpi", "title": "This quarter", "items": [
253
+ { "label": "Revenue", "value": "$1.2M", "delta": "12%", "dir": "up" },
254
+ { "label": "Churn", "value": "2.1%", "delta": "0.4pp", "dir": "down", "sub": "lower=better" } ] }
255
+ // ^ big-number metric cards with up/down/flat-tinted deltas — no chart needed.
256
+ { "type": "typography", "font": "Georgia, serif", "specimens": [
257
+ { "label": "Display", "size": "40px", "weight": "600", "text": "Ship faster" },
258
+ { "label": "Body", "size": "16px", "text": "The quick brown fox…" } ] }
259
+ // ^ type specimens at given size/weight/font — react to type like a palette.
260
+ { "type": "compare", "before": "v1.png", "after": "v2.png", "beforeLabel": "Old", "afterLabel": "New" }
261
+ // ^ before/after images with a draggable divider (redesign / before-after fix).
140
262
  { "type": "code", "lang": "js", "code": "const x = 1;", "filename": "demo.js" }
141
263
  { "type": "code", "codeFile": "src/server.js" } // load text from a local file
142
264
  { "type": "diff", "lang": "js", "filename": "src/auth.js", "view": "split",
@@ -160,6 +282,23 @@ single/multi question.
160
282
  // ^ color palettes as swatch cards: hover=hex, click=copy. Shorthand {"type":"palette","colors":[…]}; pair with a `color` question.
161
283
  ```
162
284
 
285
+ ### View a markdown file quickly
286
+
287
+ To let the user *read* a `.md` file (README, a plan you wrote, a generated
288
+ report), don't paste it into the terminal — render it:
289
+
290
+ ```sh
291
+ rly view PLAN.md # one file → board titled "PLAN.md", "Done" button
292
+ rly view README.md CHANGELOG.md # several files, each with a filename heading
293
+ rly view docs/spec.md --detach # detached like ask/show; then `rly wait <id>`
294
+ ```
295
+
296
+ `rly view` is sugar over `rly show` with `markdown` blocks (`mdFile`). The
297
+ built-in renderer is library-free and covers headings, lists, `**/_` emphasis,
298
+ code, quotes, GFM pipe tables, remote/data images, and click-to-open local
299
+ links. To mix a file into a larger board, use a `markdown` block with
300
+ `"mdFile"` alongside questions or other blocks.
301
+
163
302
  ### Visual options — show each choice, don't describe it
164
303
 
165
304
  When the options themselves are visual (design variants, layouts, color
@@ -197,6 +336,43 @@ renders as a click-to-open link that opens the file in the user's OS default app
197
336
  wrote on the board can be opened (same-origin + allowlist guarded). Surface a real
198
337
  clickable path instead of telling the user to paste it into a terminal.
199
338
 
339
+ ### Connect a question to a visual shown above (reference modal)
340
+
341
+ When a board has visuals up top and questions below, the user loses the link
342
+ between them. Give any block a stable `"ref"` name, then reference it from a
343
+ markdown link — clicking it opens that visual in a full-screen modal **in place**,
344
+ no scrolling:
345
+
346
+ ```jsonc
347
+ { "type": "chart", "ref": "velocity", "kind": "line", "labels": [...], "series": [...] }
348
+ // then in the intro, a markdown block, or a question's own markdown block:
349
+ { "type": "markdown", "md": "Decide from the [📈 Velocity](#ref:velocity) chart above." }
350
+ ```
351
+
352
+ `[label](#ref:name)` opens the block named `name`; `[label](#block:b2)` opens by
353
+ id. Use it so every question that depends on data points right at it.
354
+
355
+ ### Pin comments on a mockup (image coordinates)
356
+
357
+ Add `"pins": true` to an `image` block: the user clicks any point on the image to
358
+ drop a comment anchored to that exact spot (Figma-style), returned as an
359
+ `{kind:"image-point", x, y}` annotation. Ideal for design/mockup review.
360
+
361
+ ### Show a git diff in one step — `rly diff`
362
+
363
+ `rly diff [git args…]` runs `git diff` and opens the result as a diff board —
364
+ sugar for the "show me the diff" flow. Git args/flags pass straight through; a
365
+ multi-file diff renders with a per-file header + a jump bar.
366
+
367
+ ```sh
368
+ rly diff --detach # working-tree diff
369
+ rly diff --staged --split # staged changes, side-by-side
370
+ rly diff HEAD~1 HEAD -- src/ # a commit's diff, scoped to a path
371
+ ```
372
+
373
+ `code` blocks also support **line-anchored comments**: hover a line number to
374
+ comment on that exact line (returned as `{kind:"code-line", line}`).
375
+
200
376
  ## Annotations
201
377
 
202
378
  Users can hover chart points, diagram nodes (mermaid + graphviz), table cells,
@@ -213,7 +389,7 @@ mention annotation in the board intro.
213
389
  "id": "a1",
214
390
  "questionId": "q-id or null",
215
391
  "blockId": "b2",
216
- "target": { "kind": "chart-element | mermaid-node | graphviz-node | table-cell | text | html-element | image", "..." },
392
+ "target": { "kind": "chart-element | mermaid-node | graphviz-node | table-cell | text | html-element | image | image-point | code-line", "..." },
217
393
  "text": "user comment",
218
394
  "author": "user",
219
395
  "createdAt": "ISO",
package/src/cli.js CHANGED
@@ -236,6 +236,61 @@ async function cmdAsk(args, mode) {
236
236
  return runOrDetach(record, args);
237
237
  }
238
238
 
239
+ // `rly diff [git args…]` — run `git diff` for the user and open the result as a
240
+ // diff-block board in one step (sugar like `rly view`). Everything that isn't a
241
+ // recognized rly flag is forwarded to git verbatim, so `rly diff --staged`,
242
+ // `rly diff HEAD~1`, `rly diff main -- src/` all work. rly flags: --detach,
243
+ // --no-open, --split (side-by-side), --title.
244
+ async function cmdDiff(rest) {
245
+ const RLY_FLAGS = new Set(['--detach', '--no-open', '--split']);
246
+ const gitArgs = [];
247
+ const rlyArgv = [];
248
+ for (let i = 0; i < rest.length; i++) {
249
+ const t = rest[i];
250
+ if (t === '--title') { rlyArgv.push(t, rest[++i]); continue; }
251
+ if (t.startsWith('--title=')) { rlyArgv.push(t); continue; }
252
+ if (RLY_FLAGS.has(t)) { rlyArgv.push(t); continue; }
253
+ gitArgs.push(t);
254
+ }
255
+ const args = parseArgs(rlyArgv);
256
+ const res = spawnSync('git', ['--no-pager', 'diff', ...gitArgs], { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
257
+ if (res.error) throw new CliError(`could not run git: ${res.error.message}. Is git installed and is this a repo?`);
258
+ if (res.status !== 0 && res.stderr) throw new CliError(`git diff failed: ${res.stderr.trim()}`, res.status || 1);
259
+ const diff = res.stdout || '';
260
+ if (!diff.trim()) {
261
+ printJson({ status: 'no-diff', hint: `git diff${gitArgs.length ? ' ' + gitArgs.join(' ') : ''} produced no output — nothing to show` });
262
+ return 0;
263
+ }
264
+ const title = args.title || ('git diff' + (gitArgs.length ? ' ' + gitArgs.join(' ') : ''));
265
+ const spec = normalizeSpec({ title, blocks: [{ type: 'diff', diff, view: args.split ? 'split' : 'unified' }] });
266
+ const record = createBoard(spec);
267
+ return runOrDetach(record, args);
268
+ }
269
+
270
+ // `rly view <file.md> [more.md …]` — quick read-only board that renders one or
271
+ // more markdown files (README, plan, report) with the built-in no-library
272
+ // renderer. Each file becomes a markdown block; with 2+ files a small filename
273
+ // heading separates them. Sugar over `rly show` with markdown mdFile blocks.
274
+ async function cmdView(args) {
275
+ const files = args._;
276
+ if (!files.length) throw new CliError('usage: rly view <file.md|.csv|.json> [more …] [--title T] [--detach]');
277
+ const multi = files.length > 1;
278
+ const DATA_EXT = new Set(['csv', 'tsv', 'json']);
279
+ const blocks = [];
280
+ for (const f of files) {
281
+ if (multi) blocks.push({ type: 'markdown', md: `## ${path.basename(f)}` });
282
+ const ext = path.extname(f).slice(1).toLowerCase();
283
+ if (DATA_EXT.has(ext)) blocks.push({ type: 'table', rowsFile: f, sortable: true, filterable: true, exportable: true });
284
+ else blocks.push({ type: 'markdown', mdFile: f });
285
+ }
286
+ const raw = { blocks };
287
+ raw.title = args.title || (multi ? `${files.length} files` : path.basename(files[0]));
288
+ raw.submitLabel = args.submitLabel || 'Done';
289
+ const spec = normalizeSpec(raw); // reads + validates each file, clear error if unreadable
290
+ const record = createBoard(spec);
291
+ return runOrDetach(record, args);
292
+ }
293
+
239
294
  // Seeds the draft from the last result (as runBoard would on reopen) and
240
295
  // appends agent replies to the matching annotations, so an agent can ANSWER
241
296
  // the user's element comments and re-open the board as a conversation.
@@ -798,24 +853,42 @@ function stampSkillVersion() {
798
853
  // AGENTS.md, …). A skill is only a tier-1 hint that agents can ignore; these
799
854
  // rules, appended to the instructions the agent always reads, enforce usage.
800
855
  // Printed to stdout so `rly skill rules >> CLAUDE.md` is the whole install.
801
- const SKILL_RULES = `## relay — ask & show in the browser, not the terminal
802
-
803
- - Need the user's decisions, feedback, requirements, or approval (anything
804
- beyond one trivial yes/no)? Do NOT ask in plain terminal text **and do NOT use
805
- the native \`AskUserQuestion\` / ask-user tool** for anything beyond one
806
- trivial yes/no, open a relay board. Build a board spec and run:
807
- \`rly ask --file spec.json --detach\`, then \`rly wait <boardId>\`. **This holds
808
- in plan mode too** route clarifying/decision questions through relay there as
809
- well; reserve \`ExitPlanMode\` for the final plan-approval gate only.
810
- - Presenting a plan, structure, architecture, data, or a prototype? Show a
811
- relay board with mermaid/graphviz/chart/table/code/diff/video/image/html
812
- blocks never ASCII diagrams or walls of prose.
813
- - "Show me the diff / git diff / these changes"? Run \`git diff\` (or \`git show\`)
814
- and render the output in a \`diff\` block never paste a raw diff in the
815
- terminal. Point the user at a file with a clickable local path in a markdown
816
- block, and embed a screen recording with a \`video\` block.
817
- - When answer choices are visual (designs, layouts, variants), give each
856
+ const SKILL_RULES = `## relay — show it in the browser, don't dump it in the terminal
857
+
858
+ **Why:** your output surface a terminal, an IDE side-panel (VS Code, JetBrains,
859
+ Cursor), a plain-text chat can't actually render a markdown file, an image, a
860
+ chart, a table, a diagram, or a diff. It shows them as walls of monospace text, or
861
+ not at all (a terminal can't display a PNG). relay opens a real browser board that
862
+ renders them properly AND reads the user's reply back as JSON. **Rule of thumb: if
863
+ what you're about to output would look better than monospace text — or is a file
864
+ the user should view put it in relay instead of printing it.**
865
+
866
+ - **Markdown file / doc / README / plan / report** → \`rly view file.md\` (renders it
867
+ in the browser, no library; \`.csv\`/\`.json\` a sortable table). Never \`cat\` a
868
+ doc into the terminal for the user to read.
869
+ - **Image / screenshot / mockup / diagram / chart / table** → a relay board with an
870
+ \`image\` block (or \`mermaid\`/\`graphviz\`/\`chart\`/\`table\`/\`kpi\`/\`compare\`). The
871
+ terminal can't show pixels; relay can with click-to-zoom and comment-in-place.
872
+ - **The user's decisions, feedback, requirements, or approval** (anything beyond one
873
+ trivial yes/no) → a relay board, NOT plain terminal text and NOT the native
874
+ \`AskUserQuestion\` / ask-user tool. Build a spec and run
875
+ \`rly ask --file spec.json --detach\`, then \`rly wait <boardId>\`. **Holds in plan
876
+ mode too**; reserve \`ExitPlanMode\` for the final plan-approval gate only.
877
+ - **A plan, structure, architecture, data, or prototype** → a relay board with
878
+ diagram/chart/table/code/image/html blocks — never ASCII diagrams or walls of prose.
879
+ - **"Show me the diff / git diff / these changes"** → \`rly diff\` (runs git diff →
880
+ a diff board), or render diff text in a \`diff\` block — never paste a raw diff.
881
+ - Point the user at a file with a clickable local path in a markdown block; embed a
882
+ screen recording with a \`video\` block; when answer choices are visual, give each
818
883
  option its own visual (\`options[].blocks\`) so the user picks by looking.
884
+ - **There's a purpose-built component for most content — use the MOST SPECIFIC one,
885
+ never plain prose when a block fits.** Blocks: \`table\` (sortable/filterable/CSV,
886
+ load from .csv/.json), \`chart\`, \`kpi\` (stat cards), \`mermaid\`/\`graphviz\`/\`plantuml\`,
887
+ \`code\`, \`diff\`, \`image\` (+\`pins\`), \`compare\` (before/after), \`video\`, \`palette\`,
888
+ \`typography\`, \`html\`. Question types: \`single\`/\`multi\`/\`yesno\`/\`scale\`/\`color\`/
889
+ \`text\`/\`textarea\` plus \`rank\` (prioritize), \`allocate\` (split a budget), \`checklist\`
890
+ (per-item sign-off). For a business user, reach for \`kpi\`+\`chart\`+\`table\` and
891
+ \`rank\`/\`allocate\`/\`checklist\`; full menu + fields: \`rly agent\` and \`rly schema\`.
819
892
  - Read the result JSON from stdout; treat \`comment\` and \`annotations\` as
820
893
  first-class feedback. Full guide: \`rly agent\`.`;
821
894
 
@@ -1296,6 +1369,10 @@ USAGE
1296
1369
  rly ask ... --detach no blocking: prints {boardId,url} now; collect via \`rly wait <id>\`
1297
1370
  rly ask ... --on-result "<cmd>" push-wake: run <cmd> when the board finishes (result JSON on stdin)
1298
1371
  rly show --html-file viz.html visualization-only board (submit button = acknowledge)
1372
+ rly view <file.md> [more.md …] quick read-only board rendering markdown file(s) (no lib)
1373
+ (.csv/.tsv/.json render as a filterable, sortable table)
1374
+ rly diff [git args…] run git diff and show it in a diff board (--split, --detach,
1375
+ --title; other args pass to git: rly diff --staged | HEAD~1 | -- path)
1299
1376
  rly wait <id> [--timeout 3600] block until board finishes, print result JSON
1300
1377
  --while-active [--idle-grace 180]: keep waiting past the deadline
1301
1378
  while the user is still viewing/focused & recently active
@@ -1366,6 +1443,10 @@ export async function main(argv) {
1366
1443
  return await cmdAsk(parseArgs(rest), 'ask');
1367
1444
  case 'show':
1368
1445
  return await cmdAsk(parseArgs(rest), 'show');
1446
+ case 'view':
1447
+ return await cmdView(parseArgs(rest));
1448
+ case 'diff':
1449
+ return await cmdDiff(rest);
1369
1450
  case 'reopen':
1370
1451
  return await cmdReopen(parseArgs(rest));
1371
1452
  case 'rescue':