@khanglvm/relay 0.14.2 → 0.16.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
@@ -78,7 +78,7 @@ unchanged; pick whichever surface fits.
78
78
  | "Here's the diff — paste it in your editor" | Side-by-side **diff** blocks, syntax-highlighted **code**, **video** walkthroughs, and **file paths you click to open** in the default app |
79
79
  | "Which commits should I pick? Resolve this conflict manually." | `rly git pick` / `rly git cherry-pick` boards and color-coded `git-conflict` resolvers with ours/theirs/custom hunk choices |
80
80
  | "Type *done* when finished reviewing" | A Submit button; answers, notes, and inline comments returned as JSON |
81
- | Feedback = another wall of text | Click any chart point, diagram node, table cell, or sentence to comment — the agent replies and the thread grows on the board |
81
+ | Feedback = another wall of text | Click any chart point, diagram node, table cell, or sentence; hold+drag an image area to attach a local crop — the agent replies and the thread grows on the board |
82
82
 
83
83
  Everything autosaves in real time, detached board links keep serving after
84
84
  agent timeouts, multiple boards run at once, and the package has **zero npm
@@ -93,8 +93,9 @@ actually needs them.
93
93
  | `rly help` | every command at a glance |
94
94
  | `rly git pick` / `rly git cherry-pick` | choose commit actions and rank the order directly on a board; add `--code` to cherry-pick with split code review and per-hunk Apply/Skip/Hold |
95
95
  | `rly git conflict [files…]` | auto-detect unmerged conflict files, or open specific local paths, and return resolved content in `result.blockEdits` |
96
- | `rly share <board-id>` | activate/list/revoke same-Wi-Fi reviewer or collaborator links for a running browser board |
96
+ | `rly share <board-id>` | activate/list/revoke same-Wi-Fi collaborator, reference-only reviewer, or read-only links for a running browser board |
97
97
  | `rly view <file.md> …` | open a quick read-only board that renders local markdown file(s), data files, or PDFs — library-free; great for plans, READMEs, reports, quotes |
98
+ | `rly show --file spec.json --display-only` | present a browser board and continue immediately — no comment/note or acknowledgement required; inline `relay_show` uses this mode by default |
98
99
  | `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` |
99
100
  | `rly upgrade` | update the CLI **and** refresh the skill in one step (safe around open boards; `--dry-run`, `--cli-only`, `--skill-only`) |
100
101
  | `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 |
@@ -110,6 +111,19 @@ npm test # smoke tests with no external services (spawns real servers, fake-
110
111
 
111
112
  ## Changelog
112
113
 
114
+ ### 0.16.0 — native viewer chrome, area comments, and durable agent waits
115
+
116
+ - Viewer toolbars now use browser-native sticky positioning instead of a
117
+ scroll-event counter-translation; open comment composers re-anchor on scroll
118
+ and preserve unsaved per-target drafts.
119
+ - Hold then drag on an image to comment on an exact rectangular area. Relay
120
+ saves the crop beside the browser-board record and returns its path; comparison
121
+ crops also identify and capture the selected Before/After source.
122
+ - Browser boards support `responseRequired:false` / `--display-only`; inline
123
+ `relay_show` is display-only by default. Board and waiter deadlines now default
124
+ to one day (`--timeout 0` removes Relay's deadline), with client-specific
125
+ background/polling guidance in the agent docs.
126
+
113
127
  ### 0.11.0 — render inline inside the Claude & Codex apps (MCP App)
114
128
  - **`rly mcp` — relay as an MCP App** ([SEP-1865](https://modelcontextprotocol.io/seps/1865-mcp-apps-interactive-user-interfaces-for-mcp),
115
129
  extension `io.modelcontextprotocol/ui`). A stdio MCP server with no npm runtime dependencies
@@ -177,7 +191,8 @@ npm test # smoke tests with no external services (spawns real servers, fake-
177
191
  whose connection drops blocks further input instead of losing it, and
178
192
  `rly rescue <id>` re-serves on the same port so an open tab reconnects. Active
179
193
  same-Wi-Fi share links stay tied to the board and survive a same-port re-serve
180
- until revoked.
194
+ until revoked. Reviewer submissions are isolated, reference-only side reviews
195
+ that never finish the owner's board; read-only links cannot mutate feedback.
181
196
  - Still **zero npm runtime dependencies**, offline, and cross-platform. Vendored
182
197
  browser libraries are loaded only for boards that need them.
183
198
 
package/docs/AGENT.md CHANGED
@@ -54,7 +54,7 @@ stderr. Exit codes: `0` submitted/acknowledged · `2` timeout · `3` cancelled
54
54
  prints the result JSON:
55
55
 
56
56
  ```sh
57
- rly ask --file spec.json --timeout 1800
57
+ rly ask --file spec.json --timeout 86400 # one day; 0 = no Relay deadline
58
58
  ```
59
59
 
60
60
  **2. Detached (recommended when your shell tool has an execution time limit).**
@@ -62,7 +62,7 @@ Returns immediately with the board URL; collect later:
62
62
 
63
63
  ```sh
64
64
  rly ask --file spec.json --detach # → {"status":"open","boardId":"b-…","url":"…"}
65
- rly wait b-xxxxx --timeout 3500 # blocks until submit, prints result JSON
65
+ rly wait b-xxxxx --timeout 86400 # default: one day; pass 0 for no deadline
66
66
  rly result b-xxxxx # non-blocking peek; while open it includes
67
67
  # the live autosaved draft of the user's answers
68
68
  ```
@@ -79,19 +79,24 @@ click the topbar **Share** icon, or you can manage the same links for the user:
79
79
 
80
80
  ```sh
81
81
  rly share b-xxxxx # list active share roles
82
- rly share b-xxxxx --role review # reviewer: add comments only
83
- rly share b-xxxxx --role collab # collaborator: edit/comment/submit
82
+ rly share b-xxxxx --role review # reviewer: answer/comment + submit a side review
83
+ rly share b-xxxxx --role collab # collaborator: edit/comment/final-submit as owner-authorized
84
+ rly share b-xxxxx --role read # read-only viewer: no answers/comments/submit
84
85
  rly share b-xxxxx --role review --revoke # revoke one role
85
86
  rly share b-xxxxx --revoke --all # revoke all active links
86
87
  ```
87
88
 
88
- Use `review` unless the user explicitly wants the other device to submit as the
89
- owner. Reviewers cannot change answers, submit, edit comments, or delete
90
- comments; their autosaves merge new comments into the existing draft.
91
- Collaborators can edit answers, comment, open allowed local file links, and
92
- submit. Shared viewers refresh from the live draft when another viewer saves,
93
- deferred while someone is typing/commenting. Active share links keep the same
94
- token across a same-port `rly reopen`/`rly rescue` until revoked.
89
+ Use `review` for independent feedback: each browser gets an isolated review
90
+ draft and may answer, comment, and submit, but the submission is stored as a
91
+ **reference-only side review**. It does not finish the board, wake the agent, or
92
+ complete `rly wait`; the owner (or an owner-authorized collaborator) must still
93
+ submit the final answer. `rly result <id>` exposes current side-review drafts and
94
+ submissions on demand under `sideReviews`, clearly marked `referenceOnly:true`.
95
+ Use `read` when the other device should only view the board and existing
96
+ feedback. Read-only viewers cannot answer, comment, edit blocks, open local file
97
+ links, or submit. Collaborators retain owner-authorized edit/comment/final-submit
98
+ permission. Active links keep the same token across a same-port
99
+ `rly reopen`/`rly rescue` until revoked.
95
100
 
96
101
  ## Inline mode — relay as an MCP App (Claude & Codex apps)
97
102
 
@@ -135,8 +140,9 @@ Both take **the exact same board spec** documented below (the tool `inputSchema`
135
140
  fills it in, and their answers come back to you (answers, per-question notes,
136
141
  comment) as a user-message turn, with structured context synced when the host
137
142
  supports it — read them just as you would the CLI's result JSON. There is **no
138
- `--detach`/`rly wait` dance, no stdout parsing, and no timeout** in this mode; the
139
- board stays live until the user submits and the host delivers the result.
143
+ `--detach`/`rly wait` dance, no stdout parsing, and no timeout** in this mode.
144
+ `relay_ask` stays live until the user submits and the host delivers the result;
145
+ `relay_show` is display-only by default and the agent continues immediately.
140
146
 
141
147
  **Near-full parity with the browser board.** Local `codeFile` / `htmlFile` /
142
148
  `diffFile` and local **image** files work inline too — the server inlines them
@@ -161,12 +167,25 @@ rly ask -q "Deploy to prod now?::yesno" -q "!Environment::single::dev,staging,pr
161
167
  # label::type::comma,separated,options leading "!" = required
162
168
  ```
163
169
 
164
- Visualization-only (no questions; submit button reads "Acknowledge"):
170
+ Visualization-only, with acknowledgement/feedback:
165
171
 
166
172
  ```sh
167
173
  rly show --html-file prototype.html --title "Dashboard concept" --height 600
168
174
  ```
169
175
 
176
+ Display-only (the agent continues immediately; no note, comments, or
177
+ Submit/Acknowledge action):
178
+
179
+ ```sh
180
+ rly show --file spec.json --display-only
181
+ # equivalent spec field: {"responseRequired": false, "blocks": [...]}
182
+ ```
183
+
184
+ `--display-only` automatically detaches the browser board. In MCP App mode,
185
+ `relay_show` is display-only by default; set `responseRequired:true` only when
186
+ the user really needs to acknowledge or leave feedback. `relay_ask` remains the
187
+ response-bearing tool.
188
+
170
189
  Read a markdown file (no questions; library-free renderer; submit reads "Done"):
171
190
 
172
191
  ```sh
@@ -214,6 +233,7 @@ choices plus the full resolved file in `result.blockEdits[blockId]`:
214
233
  "allowPartial": true, // default true: user may submit with gaps -> "skipped"
215
234
  "note": true, // default true: optional free-text box -> result "comment"
216
235
  "autoClose": true, // default true: tab tries to close itself after submit
236
+ "responseRequired": true, // false = display-only; no feedback/action, agent continues
217
237
  "submitLabel": "Submit",
218
238
  "questions": [
219
239
  { "id": "approach", "type": "single", "label": "Which approach?", "required": true,
@@ -516,11 +536,11 @@ the question-types section above.
516
536
  | `diff` | proposed code changes / before-after — a unified diff rendered as a colored git-style comparison (no git needed) |
517
537
  | `video` | demos, screen recordings, walkthroughs — YouTube/Vimeo embeds, a media URL, or a local video file (streamed) |
518
538
  | `pdf` | quotes, reports, exports, forms — local `.pdf` files or PDF URLs rendered inline; local files stream from the board server |
519
- | `image` | screenshots, mockup exports, photos — local files embed and work offline |
539
+ | `image` | screenshots, mockup exports, photos — local files embed and work offline; hold then drag to comment on an exact area |
520
540
  | `palette` | color palettes / themes — swatch cards with hover-hex + click-to-copy; pair with a `color` question to let the user pick |
521
541
  | `kpi` | big-number metric cards (`items:[{label,value,delta?,dir?,sub?}]`) with up/down/flat-tinted deltas — at-a-glance numbers without a chart |
522
542
  | `typography` | type specimens (`specimens:[{label?,size?,weight?,font?,text?}]`) — react to type choices like a palette |
523
- | `compare` | before/after images with a draggable divider (`before`/`after` = url/path/`{src,label}`) redesigns, before-after fixes |
543
+ | `compare` | before/after images with a draggable divider (`before`/`after` = url/path/`{src,label}`); area comments preserve which side was selected |
524
544
  | `html` | anything else — pixel-perfect mockups, custom widgets, embeds |
525
545
 
526
546
  Two cross-block fields work on **any** block: `"ref":"name"` makes it openable in
@@ -529,6 +549,14 @@ shown earlier — no scrolling); `image` blocks also take `"pins":true` for
529
549
  click-to-drop coordinate pin-comments. `table` blocks take `"rowsFile"` (load
530
550
  rows from .csv/.tsv/.json), `"filterable"`, and `"exportable"` (CSV download).
531
551
 
552
+ Every interactive `image` and `compare` block also supports area feedback:
553
+ hold briefly, then drag a rectangle. Relay saves a pixel crop beside the board
554
+ record and returns its local path with the annotation, so the agent can open the
555
+ exact pixels instead of inferring from a whole-image comment. In a comparison,
556
+ the side visible where the hold began is recorded as `before` or `after` and the
557
+ crop comes from that source image. Remote images that prevent browser canvas
558
+ access still return coordinates and a `cropUnavailable` marker.
559
+
532
560
  ### Height rules
533
561
 
534
562
  - `markdown`, `code`: natural flow (no fixed height).
@@ -628,6 +656,8 @@ intro. Annotations are autosaved with the draft and returned in the final result
628
656
  | `text` | `quote`, `prefix` (≤30 chars before), `suffix` (≤30 after) | selecting text in a markdown block |
629
657
  | `html-element` | `ref`, `label`, `detail?` | hovering any element in a custom-HTML block (auto), or a `data-relay-annotate` / `relayKit.commentable()` element |
630
658
  | `image` | `label` | clicking a PlantUML diagram or an image block |
659
+ | `image-point` | `x`, `y`, `label` | clicking an image authored with `pins:true` |
660
+ | `image-region` | `x`, `y`, `w`, `h`, `label`, optional `side`, `crop` | holding then dragging on an image or comparison; `crop.path` is the local image artifact in browser mode |
631
661
 
632
662
  Read annotations as first-class feedback — they often carry the sharpest insight
633
663
  (e.g. a user circling the one data point that concerns them, or quoting the exact
@@ -658,14 +688,15 @@ time). Use it instead of guessing timeouts:
658
688
  ```sh
659
689
  rly result b-xxxxx # open board → includes "presence":
660
690
  # {open, seen, visible, focused, secondsSinceActivity, secondsSincePing}
661
- rly wait b-xxxxx --timeout 550 --while-active --idle-grace 180
691
+ rly wait b-xxxxx --timeout 86400 --while-active --idle-grace 3600
662
692
  ```
663
693
 
664
694
  `--while-active` keeps extending the wait as long as the user is demonstrably
665
695
  active (page visible/focused and interaction within `--idle-grace` seconds,
666
- default 180); once they go idle it returns the normal `wait-timeout` JSON,
667
- with `presence` attached so you can decide what to do next. Prefer this over
668
- raising `--timeout`.
696
+ default 180); once the deadline is reached and they are idle it returns the
697
+ normal `wait-timeout` JSON, with `presence` attached. Relay now defaults both a
698
+ board and `rly wait` to 86,400 seconds (one day); pass `--timeout 0` for no Relay
699
+ deadline when the host can safely own a long-running waiter.
669
700
 
670
701
  ### A `timeout` on a detached board is NOT the end
671
702
 
@@ -680,7 +711,7 @@ tells them you stopped waiting and to prompt you afterward. So if you got a
680
711
  so a late submit push-wakes you. A blocking `rly ask` (no `--detach`) still ends
681
712
  hard on timeout, since there's no separate waiter to hand back to.
682
713
 
683
- ## Push-wake get notified instead of polling
714
+ ## Waiting and push-wake across agent clients
684
715
 
685
716
  ```sh
686
717
  rly ask --file spec.json --detach --on-result 'curl -s -X POST localhost:9999/wake -d @-'
@@ -692,7 +723,19 @@ the board reaches a terminal status — submitted, acknowledged, timeout, or
692
723
  cancelled — with the full result JSON piped to stdin and `RLY_BOARD_ID`,
693
724
  `RLY_STATUS`, `RLY_URL` in the environment. `--notify-cmd` does the same from
694
725
  a `wait` that obtains a terminal result. Write a file your harness watches,
695
- hit a webhook — whatever wakes you.
726
+ hit a webhook — whatever wakes you. This is passive only when that client or
727
+ harness has an actual inbound wake mechanism; Relay does not pretend that a
728
+ file write alone starts a new model turn.
729
+
730
+ | Client/surface | Recommended Relay return path |
731
+ |---|---|
732
+ | MCP App (`relay_ask`) | No CLI wait. The inline board stays live and sends `ui/message` on submit, which starts the user-return turn. |
733
+ | MCP App (`relay_show`) | Display-only by default. Present it and continue immediately; no acknowledgement is requested. |
734
+ | Claude Code | Run `rly wait <id> --timeout 0` as a [background Bash task](https://docs.anthropic.com/en/docs/claude-code/interactive-mode#background-bash-commands); retain its task id/output rather than opening a second board. |
735
+ | Gemini CLI | Run the waiter in the background and set [`tools.shell.backgroundCompletionBehavior` to `"inject"`](https://geminicli.com/docs/reference/configuration/) so completion is returned to the agent; `"notify"` only informs the chat UI. |
736
+ | Codex | Keep one foreground waiter. Codex background terminals use explicit `write_stdin` polling (the [poll window defaults to five minutes](https://developers.openai.com/codex/config-reference)); the process can outlive each poll, but no portable browser-submit API wakes a stopped turn. |
737
+ | OpenCode | Core Bash is foreground. Its [ecosystem](https://opencode.ai/docs/ecosystem/) lists background-PTY extensions; use one only when installed, otherwise keep the waiter foreground. |
738
+ | Pi | Core intentionally has [no background Bash](https://github.com/badlogic/pi-mono/blob/main/packages/coding-agent/docs/usage.md); keep the waiter foreground or use an explicitly installed background-task extension. |
696
739
 
697
740
  ### Codex browser-board pattern
698
741
 
@@ -704,7 +747,7 @@ submits:
704
747
 
705
748
  ```sh
706
749
  rly ask --file spec.json --detach
707
- rly wait b-xxxxx --timeout 1800 --while-active --idle-grace 300
750
+ rly wait b-xxxxx --timeout 86400 --while-active --idle-grace 3600
708
751
  ```
709
752
 
710
753
  If `rly wait` exits with `wait-timeout`, immediately run `rly result <boardId>`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@khanglvm/relay",
3
- "version": "0.14.2",
3
+ "version": "0.16.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",
@@ -85,8 +85,8 @@ priority call in a `rank` question — not in paragraphs. Unsure which exists? R
85
85
  | `code` | source / config / command output — highlighted, line numbers, hover-a-line to comment |
86
86
  | `diff` | code changes — colored unified/split, multi-file (`rly diff` builds the whole board) |
87
87
  | `git-conflict` | conflict-marker files — side-by-side ours/theirs/base with hunk choices; returns resolved content in `result.blockEdits[blockId]` |
88
- | `image` | screenshots / mockups / renders — zoom+pan; `pins:true` → click-to-drop point comments |
89
- | `compare` | a before/after pair — draggable divider |
88
+ | `image` | screenshots / mockups / renders — zoom+pan; hold+drag → area crop comment; `pins:true` → point comments |
89
+ | `compare` | a before/after pair — draggable divider; area comments retain the selected side |
90
90
  | `video` | a demo / screen recording / walkthrough |
91
91
  | `pdf` | a quote / report / exported document that should render inline |
92
92
  | `palette` | color schemes — swatch cards, hover-hex, click-to-copy |
@@ -130,7 +130,7 @@ blocking `rly ask` that gets killed cancels the board. Always prefer:
130
130
 
131
131
  ```sh
132
132
  rly ask --file spec.json --detach # returns {"boardId":"b-…","url":…} immediately
133
- rly wait b-xxxxx --timeout 550 # blocks until submit, prints result JSON
133
+ rly wait b-xxxxx --timeout 86400 # default: one day; 0 = no Relay deadline
134
134
  # (on exit 2 "wait-timeout" just run wait again)
135
135
  rly result b-xxxxx # non-blocking peek (includes live draft)
136
136
  ```
@@ -142,7 +142,7 @@ create a new board just because a wait timed out.
142
142
  For long waits prefer presence-aware waiting over a huge --timeout:
143
143
 
144
144
  ```sh
145
- rly wait b-xxxxx --timeout 550 --while-active --idle-grace 180
145
+ rly wait b-xxxxx --timeout 86400 --while-active --idle-grace 3600
146
146
  # keeps extending while the user is demonstrably viewing/typing on the board;
147
147
  # returns wait-timeout promptly once they are idle/gone (presence included)
148
148
  rly result b-xxxxx # while open also shows presence {visible, focused, secondsSinceActivity}
@@ -159,7 +159,7 @@ So in Codex, keep the waiter in the foreground until the user submits:
159
159
 
160
160
  ```sh
161
161
  rly ask --file spec.json --detach
162
- rly wait b-xxxxx --timeout 1800 --while-active --idle-grace 300
162
+ rly wait b-xxxxx --timeout 86400 --while-active --idle-grace 3600
163
163
  ```
164
164
 
165
165
  If `rly wait` exits with `wait-timeout`, immediately run `rly result <boardId>`.
@@ -168,9 +168,30 @@ If it is still open and the user may continue, run `rly wait` again. Do not use
168
168
  webhooks, but normal Codex CLI sessions do not expose a portable inbound API
169
169
  that wakes the current agent turn.
170
170
 
171
- Blocking mode (`rly ask --file spec.json --timeout 1800`, no --detach) is fine
171
+ Blocking mode (`rly ask --file spec.json --timeout 86400`, no `--detach`) is fine
172
172
  ONLY when your shell tool has no execution time limit.
173
173
 
174
+ Relay defaults board and waiter deadlines to one day; use `--timeout 0` when the
175
+ client can safely own an indefinite process. Choose the return path by host:
176
+
177
+ - Inline MCP `relay_ask`: no CLI wait; its `ui/message` submission wakes the
178
+ return turn. Inline `relay_show` is display-only by default and does not wait.
179
+ - Claude Code: background `rly wait <id> --timeout 0` as a background Bash task.
180
+ - Gemini CLI: background the waiter and use
181
+ `tools.shell.backgroundCompletionBehavior: "inject"`.
182
+ - Codex: keep one foreground waiter; background terminals require explicit
183
+ polling and do not expose a portable browser-submit wake API.
184
+ - OpenCode/Pi: keep it foreground unless a background-task extension is
185
+ explicitly installed (Pi core has no background Bash).
186
+
187
+ Use `--on-result`/`--notify-cmd` only when the surrounding harness exposes a real
188
+ webhook, file watcher, or inbound wake path.
189
+
190
+ To present without asking for any response, use
191
+ `rly show --file spec.json --display-only` (equivalent to
192
+ `"responseRequired":false`). It returns immediately and shows no note,
193
+ comments, or Submit/Acknowledge action.
194
+
174
195
  Useful flags: `--no-open` (don't auto-open the browser — for tests/CI; real
175
196
  users need the tab, so omit it normally) · `--title` · `--timeout <sec>`.
176
197
 
@@ -189,16 +210,22 @@ with `rly share`:
189
210
  rly share b-xxxxx # list active share roles
190
211
  rly share b-xxxxx --role review # activate a reviewer link
191
212
  rly share b-xxxxx --role collab # activate a collaborator link
213
+ rly share b-xxxxx --role read # activate a read-only link
192
214
  rly share b-xxxxx --role review --revoke # revoke one role
193
215
  rly share b-xxxxx --revoke --all # revoke all active share links
194
216
  ```
195
217
 
196
- Use `review` when the other device should add comments only. Reviewers cannot
197
- change answers, submit, edit comments, or delete comments; their autosaves merge
198
- new comments into the existing draft. Use `collab` only when the user explicitly
218
+ Use `review` for independent feedback. Each reviewer browser gets an isolated
219
+ draft and can answer, comment, and submit a **reference-only side review**. That
220
+ submission never finishes the board or wakes/completes the waiting agent; the
221
+ owner (or owner-authorized collaborator) must still provide the final answer.
222
+ When the owner asks for current reviewer input, run `rly result <id>` and inspect
223
+ `sideReviews.submissions` plus `sideReviews.drafts`; both are explicitly marked
224
+ reference-only. Use `read` for a view-only link with no answers, comments, block
225
+ edits, local-file opening, or submit. Use `collab` only when the user explicitly
199
226
  wants that device to act as the owner: collaborators can edit answers, comment,
200
- open allowed local file links, and submit. Shared viewers refresh from the live
201
- draft when another viewer saves, deferred while someone is typing/commenting.
227
+ open allowed local file links, and final-submit. Shared viewers refresh from the
228
+ relevant live draft when another viewer in the same role/session saves.
202
229
  Active share links are durable too: they keep the same token across a same-port
203
230
  `rly reopen`/`rly rescue` until `rly share --revoke` disables them.
204
231
 
@@ -412,6 +439,15 @@ Add `"pins": true` to an `image` block: the user clicks any point on the image t
412
439
  drop a comment anchored to that exact spot (Figma-style), returned as an
413
440
  `{kind:"image-point", x, y}` annotation. Ideal for design/mockup review.
414
441
 
442
+ ### Comment on an exact image area (local crop)
443
+
444
+ Every interactive `image` and `compare` block supports hold-then-drag area
445
+ feedback without another spec flag. Relay returns an `image-region` target with
446
+ normalized `x`, `y`, `w`, and `h`; browser boards also save the selected pixels
447
+ beside the board and return `target.crop.path`, which the agent should open with
448
+ its image viewer. Comparison targets include `side:"before"|"after"`, and the
449
+ crop comes from that source image rather than the composited slider view.
450
+
415
451
  ### Show a git diff in one step — `rly diff`
416
452
 
417
453
  `rly diff [git args…]` runs `git diff` and opens the result as a diff board —
@@ -443,7 +479,7 @@ mention annotation in the board intro.
443
479
  "id": "a1",
444
480
  "questionId": "q-id or null",
445
481
  "blockId": "b2",
446
- "target": { "kind": "chart-element | mermaid-node | graphviz-node | table-cell | text | html-element | image | image-point | code-line", "..." },
482
+ "target": { "kind": "chart-element | mermaid-node | graphviz-node | table-cell | text | html-element | image | image-point | image-region | code-line", "..." },
447
483
  "text": "user comment",
448
484
  "author": "user",
449
485
  "createdAt": "ISO",
package/src/cli.js CHANGED
@@ -28,6 +28,8 @@ const BIN = path.join(PKG_ROOT, 'bin', 'rly.js');
28
28
  const PKG_JSON = JSON.parse(fs.readFileSync(path.join(PKG_ROOT, 'package.json'), 'utf8'));
29
29
  const VERSION = PKG_JSON.version;
30
30
  const PKG_NAME = PKG_JSON.name; // e.g. "@khanglvm/relay" — the global package to upgrade
31
+ const DEFAULT_BOARD_TIMEOUT_SEC = 86400;
32
+ const DEFAULT_WAIT_TIMEOUT_SEC = 86400;
31
33
 
32
34
  const VALUED_FLAGS = new Set([
33
35
  'file', 'html', 'html-file', 'title', 'intro', 'timeout', 'port',
@@ -171,6 +173,7 @@ async function resolveSpecInput(args, mode) {
171
173
  if (args.htmlFile) raw.htmlFile = args.htmlFile;
172
174
  if (args.height) raw.htmlHeight = args.height;
173
175
  if (args.submitLabel) raw.submitLabel = args.submitLabel;
176
+ if (args.displayOnly === true) raw.responseRequired = false;
174
177
  if (args.q.length) {
175
178
  raw.questions = [...(raw.questions || []), ...args.q.map((s, i) => questionFromInline(s, i))];
176
179
  }
@@ -198,7 +201,7 @@ function readFileOrThrow(p) {
198
201
 
199
202
  async function runOrDetach(record, args) {
200
203
  await assertSpecReady(record.spec);
201
- const timeoutSec = args.timeout !== undefined ? Math.max(0, Number.parseInt(args.timeout, 10) || 0) : 1800;
204
+ const timeoutSec = args.timeout !== undefined ? Math.max(0, Number.parseInt(args.timeout, 10) || 0) : DEFAULT_BOARD_TIMEOUT_SEC;
202
205
  const port = args.port !== undefined ? Number.parseInt(args.port, 10) || 0 : 0;
203
206
  const open = args.open !== false;
204
207
 
@@ -231,7 +234,9 @@ async function runOrDetach(record, args) {
231
234
  boardId: record.id,
232
235
  url: info.url,
233
236
  port: info.port,
234
- hint: `block for the answers with: rly wait ${record.id}`,
237
+ hint: record.spec.responseRequired === false
238
+ ? 'display-only board opened; no response will be collected'
239
+ : `block for the answers with: rly wait ${record.id}`,
235
240
  });
236
241
  return 0;
237
242
  }
@@ -247,7 +252,7 @@ async function cmdAsk(args, mode) {
247
252
  const spec = normalizeSpec(raw);
248
253
  await assertSpecReady(spec);
249
254
  const record = createBoard(spec);
250
- return runOrDetach(record, args);
255
+ return runOrDetach(record, spec.responseRequired === false ? { ...args, detach: true } : args);
251
256
  }
252
257
 
253
258
  // `rly diff [git args…]` — run `git diff` for the user and open the result as a
@@ -639,11 +644,11 @@ async function fetchPresence(url) {
639
644
  async function cmdWait(args) {
640
645
  const id = args._[0];
641
646
  if (!id) throw new CliError('usage: rly wait <board-id> [--timeout <sec>] [--while-active] [--idle-grace <sec>] [--notify-cmd <cmd>]');
642
- const timeoutSec = args.timeout !== undefined ? Math.max(1, Number.parseInt(args.timeout, 10) || 1) : 3600;
647
+ const timeoutSec = args.timeout !== undefined ? Math.max(0, Number.parseInt(args.timeout, 10) || 0) : DEFAULT_WAIT_TIMEOUT_SEC;
643
648
  const whileActive = args.whileActive === true;
644
649
  const idleGrace = args.idleGrace !== undefined ? Math.max(0, Number.parseInt(args.idleGrace, 10) || 0) : 180;
645
650
  const notifyCmd = typeof args.notifyCmd === 'string' && args.notifyCmd.trim() ? args.notifyCmd : null;
646
- let deadline = Date.now() + timeoutSec * 1000;
651
+ let deadline = timeoutSec === 0 ? Number.POSITIVE_INFINITY : Date.now() + timeoutSec * 1000;
647
652
  mustLoad(id);
648
653
 
649
654
  // Push-wake: run the agent's --notify-cmd after a TERMINAL result, then print.
@@ -761,21 +766,27 @@ async function cmdWaitLoop(id, deadline, opts) {
761
766
 
762
767
  async function cmdResult(args) {
763
768
  const record = mustLoad(args._[0]);
769
+ const sideReviews = {
770
+ referenceOnly: true,
771
+ note: 'Side reviews are reference only. Wait for the board owner (or owner-authorized collaborator) to submit the final answer.',
772
+ submissions: Array.isArray(record.sideReviews) ? record.sideReviews : [],
773
+ drafts: record.reviewDrafts && typeof record.reviewDrafts === 'object' ? record.reviewDrafts : {},
774
+ };
764
775
  if (record.result && record.result.finishedAt) {
765
- printResult(record.result);
776
+ printResult({ ...record.result, sideReviews });
766
777
  return exitCodeFor(record.result.status);
767
778
  }
768
779
  const running = loadRunning(record.id);
769
780
  if (running && isAlive(running.pid)) {
770
781
  // While open, expose the real-time autosaved draft so agents can peek, plus
771
782
  // best-effort presence (whether the user is still viewing/focused/active).
772
- const out = { status: 'open', boardId: record.id, url: running.url, draft: record.draft ?? null };
783
+ const out = { status: 'open', boardId: record.id, url: running.url, draft: record.draft ?? null, sideReviews };
773
784
  const presence = await fetchPresence(running.url);
774
785
  if (presence) out.presence = presence;
775
786
  printResult(out); // draft can hold many annotations — sidecar it too
776
787
  return 0;
777
788
  }
778
- printResult({ status: 'lost', boardId: record.id, draft: record.draft ?? null });
789
+ printResult({ status: 'lost', boardId: record.id, draft: record.draft ?? null, sideReviews });
779
790
  return 5;
780
791
  }
781
792
 
@@ -785,9 +796,10 @@ async function cmdShare(args) {
785
796
  if (!running || !isAlive(running.pid)) {
786
797
  throw new CliError(`board "${record.id}" is not running. Use \`rly reopen ${record.id} --detach\` first.`, 5);
787
798
  }
788
- const role = args.all ? 'all' : args.role;
789
- if (role !== undefined && !['collab', 'review', 'all'].includes(role)) {
790
- throw new CliError('--role must be collab or review.', 4);
799
+ let role = args.all ? 'all' : args.role;
800
+ if (role === 'readonly' || role === 'read-only') role = 'read';
801
+ if (role !== undefined && !['collab', 'review', 'read', 'all'].includes(role)) {
802
+ throw new CliError('--role must be collab, review, or read.', 4);
791
803
  }
792
804
  const shareUrl = new URL('/api/share', running.url);
793
805
  let res;
@@ -1079,7 +1091,7 @@ the user should view — put it in relay instead of printing it.**
1079
1091
  user-facing "wake this agent turn from a browser submit" command. In Codex,
1080
1092
  keep the waiter in the foreground until the user submits:
1081
1093
  \`rly ask --file spec.json --detach\` then
1082
- \`rly wait <boardId> --timeout 1800 --while-active --idle-grace 300\`.
1094
+ \`rly wait <boardId> --timeout 86400 --while-active --idle-grace 3600\`.
1083
1095
  If wait exits with \`wait-timeout\`, immediately run \`rly result <boardId>\`;
1084
1096
  if it is still open and the user may continue, run \`rly wait\` again. Do not
1085
1097
  use \`--on-result\` as the primary Codex return path; normal Codex CLI sessions
@@ -1087,6 +1099,10 @@ the user should view — put it in relay instead of printing it.**
1087
1099
  Detached boards are durable: a board timeout hands the agent a \`timeout\`
1088
1100
  result but keeps the same URL/port serving until Submit or \`rly stop\`. Do
1089
1101
  not create a new board just because a wait timed out.
1102
+ - **No response needed** → \`rly show --file spec.json --display-only\`. It
1103
+ returns immediately and shows no note, comments, or Submit/Acknowledge action.
1104
+ In MCP App mode, \`relay_show\` is display-only by default; \`relay_ask\` is the
1105
+ response-bearing tool.
1090
1106
  - **A plan, structure, architecture, data, or prototype** → a relay board with
1091
1107
  diagram/chart/table/code/image/html blocks — never ASCII diagrams or walls of prose.
1092
1108
  - **"Show me the diff / git diff / these changes"** → \`rly diff\` (runs git diff →
@@ -1100,14 +1116,16 @@ the user should view — put it in relay instead of printing it.**
1100
1116
  option its own visual (\`options[].blocks\`) so the user picks by looking.
1101
1117
  - Same-Wi-Fi sharing is locked by default. When the user wants another device to
1102
1118
  review or co-fill a running board, manage explicit links with \`rly share <id>\`:
1103
- \`--role review\` creates a comments-only reviewer link, \`--role collab\` creates
1104
- an edit/comment/submit link, and \`--revoke\` disables active links. Active
1119
+ \`--role review\` creates an answer/comment link whose submissions are reference-only
1120
+ side reviews (\`rly result\` exposes them but they never complete \`rly wait\`),
1121
+ \`--role collab\` creates an owner-authorized edit/comment/final-submit link,
1122
+ \`--role read\` creates a view-only link, and \`--revoke\` disables active links. Active
1105
1123
  share links are durable across same-port \`rly reopen\`/\`rly rescue\` until
1106
1124
  revoked.
1107
1125
  - **There's a purpose-built component for most content — use the MOST SPECIFIC one,
1108
1126
  never plain prose when a block fits.** Blocks: \`table\` (sortable/filterable/CSV,
1109
1127
  load from .csv/.json), \`chart\`, \`kpi\` (stat cards), \`mermaid\`/\`graphviz\`/\`plantuml\`,
1110
- \`code\`, \`diff\`, \`git-conflict\`, \`image\` (+\`pins\`), \`compare\` (before/after), \`video\`, \`pdf\`, \`palette\`,
1128
+ \`code\`, \`diff\`, \`git-conflict\`, \`image\` (hold+drag area crops; +\`pins\` for points), \`compare\` (before/after with side-aware area crops), \`video\`, \`pdf\`, \`palette\`,
1111
1129
  \`typography\`, \`html\`. Question types: \`single\`/\`multi\`/\`yesno\`/\`scale\`/\`color\`/
1112
1130
  \`text\`/\`textarea\` plus \`rank\` (prioritize), \`allocate\` (split a budget), \`checklist\`
1113
1131
  (per-item sign-off). For a business user, reach for \`kpi\`+\`chart\`+\`table\` and
@@ -1571,7 +1589,7 @@ async function cmdServeInternal(args) {
1571
1589
  id,
1572
1590
  port: args.port !== undefined ? Number.parseInt(args.port, 10) || 0 : 0,
1573
1591
  open: args.open !== false,
1574
- timeoutSec: args.timeout !== undefined ? Math.max(0, Number.parseInt(args.timeout, 10) || 0) : 1800,
1592
+ timeoutSec: args.timeout !== undefined ? Math.max(0, Number.parseInt(args.timeout, 10) || 0) : DEFAULT_BOARD_TIMEOUT_SEC,
1575
1593
  quiet: true,
1576
1594
  // Detached board: timeout hands back to the agent but keeps the same
1577
1595
  // URL/port serving until Submit or explicit stop.
@@ -1591,7 +1609,9 @@ USAGE
1591
1609
  quick inline questions ("!" = required, label::type::options)
1592
1610
  rly ask ... --detach no blocking: prints {boardId,url} now; collect via \`rly wait <id>\`
1593
1611
  rly ask ... --on-result "<cmd>" push-wake: run <cmd> when the board finishes (result JSON on stdin)
1594
- rly show --html-file viz.html visualization-only board (submit button = acknowledge)
1612
+ rly show --html-file viz.html visualization board (submit button = acknowledge)
1613
+ rly show --file spec.json --display-only
1614
+ present without comments/acknowledgement; returns immediately
1595
1615
  rly view <file.md> [more.md …] quick read-only board rendering markdown file(s) (no lib)
1596
1616
  (.csv/.tsv/.json render as a filterable, sortable table;
1597
1617
  .pdf streams in an inline PDF viewer)
@@ -1602,7 +1622,7 @@ USAGE
1602
1622
  add --code for split code view with per-hunk Apply/Skip/Hold
1603
1623
  rly git conflict [files…] board for resolving conflict-marker files; with no files, auto-detects
1604
1624
  unmerged git files and returns resolved content in result.blockEdits
1605
- rly wait <id> [--timeout 3600] block until board finishes, print result JSON
1625
+ rly wait <id> [--timeout 86400] block up to one day until the board finishes (0 = no deadline)
1606
1626
  --while-active [--idle-grace 180]: keep waiting past the deadline
1607
1627
  while the user is still viewing/focused & recently active
1608
1628
  --notify-cmd "<cmd>": run <cmd> on a terminal result (JSON on stdin)
@@ -1610,8 +1630,9 @@ USAGE
1610
1630
  read that file if your shell truncates stdout; never pipe to head/tail
1611
1631
  rly result <id> result/status now (includes live autosaved draft + presence while open)
1612
1632
  rly share <id> list active same-Wi-Fi share links for a running board
1613
- rly share <id> --role review activate a reviewer link (comments only)
1614
- rly share <id> --role collab activate a collaborator link (edit/comment/submit)
1633
+ rly share <id> --role review reviewer: answer/comment/submit a reference-only side review
1634
+ rly share <id> --role collab collaborator: edit/comment/final-submit as owner-authorized
1635
+ rly share <id> --role read activate a read-only viewer link
1615
1636
  rly share <id> --role review --revoke
1616
1637
  revoke one share role; use --revoke --all to disable all roles
1617
1638
  active links survive same-port reopen/rescue until revoked
@@ -1645,7 +1666,7 @@ USAGE
1645
1666
 
1646
1667
  COMMON FLAGS
1647
1668
  --title <s> --intro <s> --html-file <f> --height <px> --submit-label <s>
1648
- --timeout <sec> (default 1800; 0 = none) --port <n> --no-open --detach
1669
+ --timeout <sec> (default 86400; 0 = none) --port <n> --no-open --detach --display-only
1649
1670
 
1650
1671
  EXIT CODES 0 submitted/acknowledged · 2 timeout · 3 cancelled · 4 usage · 5 not found
1651
1672