lavish-axi 0.1.78 → 0.1.80

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
@@ -53,7 +53,7 @@ npx skills add kunchenguid/lavish-axi --skill lavish
53
53
 
54
54
  That is the entire setup - no npm install needed.
55
55
  The skill teaches your agent to run Lavish through `npx -y lavish-axi`, so the CLI comes along on demand.
56
- It stays a short stub and sends the agent to `npx -y lavish-axi --help`, `design`, and `playbook` for current instructions, so an installed copy cannot go stale against a newer CLI.
56
+ It stays a short stub and sends the agent to `npx -y lavish-axi --help`, `reply --help`, `design`, and `playbook` for current instructions, so an installed copy cannot go stale against a newer CLI.
57
57
  Its frontmatter also includes Hermes Agent metadata, so Hermes-compatible harnesses can categorize and surface it as a first-class productivity skill.
58
58
  This installs the public `lavish` skill.
59
59
  The repository also contains an internal `lavish-design` brand skill for maintainers; default `npx skills add ... --list` and skills.sh discovery hide it unless `INSTALL_INTERNAL_SKILLS=1` is set.
@@ -200,6 +200,7 @@ pnpm link
200
200
  If the element an unsent annotation was attached to is gone from the artifact for good, Lavish cannot reopen that card, so it writes your text into the conversation panel under **Unsent annotation** - selectable, never written over anything you have typed, and kept there across reloads; no note is ever dropped to make room for a newer one, and a note the browser refuses to store says so where it is shown.
201
201
  - **Feedback controls** - Native controls (radios, checkboxes, inputs, selects, buttons, labels, disclosure summaries, contenteditable) are interactive automatically, so they do not need `data-lavish-action`.
202
202
  For reversible choices, let option clicks update local state, then queue exactly one final answer from a per-question submit or Queue answer button with `window.lavish.queuePrompt()`.
203
+ If someone may open a decision artifact as a standalone HTML file, `lavish-axi playbook input` provides an optional Copy all answers control they can use to paste their form answers back into chat.
203
204
  Mark only custom (non-native) clickable elements with `data-lavish-action` so Lavish does not annotate them, and use `data-lavish-question` or `queueKey` when pre-send updates for the same question should replace each other.
204
205
  Everything you send is part of the conversation: a note on an element, a text selection, a table cell, a diagram node, a whiteboard, a layout-issue batch, or a composer message each becomes a bubble on your side of the Conversation panel, with an anchor line naming what it was attached to (the element tag, text selection, table cell, diagram node, whiteboard, or layout issues), a quoted excerpt, and, when available, the selector on hover. A note you have queued but not sent sits at the end of the conversation as a dashed bubble labelled **Queued** with a remove control; while its batch is in flight it reads **Sending**, and once the server accepts it, it settles in place as a sent bubble in every tab of the session. The panel keeps the newest stored transcript up to 5 MiB; older bubbles drop from view, but an accepted note still settles and is not sent twice. Agent replies render a small Markdown subset - paragraphs, line breaks, headings as bold lead lines, bullet and numbered lists, fenced and inline code, bold, italic, and http(s) links - while tables, images, and raw HTML stay as the text the agent wrote; your own messages keep their line breaks and are never parsed.
205
206
  On wider screens, the conversation and the queued notes share one scrollable Conversation panel above a sticky composer, so long feedback queues do not push the text box or send controls off screen.
@@ -209,7 +210,7 @@ pnpm link
209
210
  - **Keyboard shortcuts** - In the chrome composer, Enter sends queued prompts and Shift+Enter inserts a newline.
210
211
  In the annotation card, Enter queues the annotation, Shift+Enter inserts a newline, and Ctrl+Enter (Cmd+Enter on macOS) queues it and sends all queued prompts immediately. Escape closes the card, same as Cancel, but only while it is empty (no text, no attachment); with unsent text or an attachment present, Escape does nothing rather than risk discarding it.
211
212
  Cmd+I or Ctrl+I toggles between annotate and explore mode from either the browser chrome or the artifact iframe, including while focus is in a textarea or control.
212
- - **Agent presence** - The browser shows when no agent is listening, keeps queued feedback for the next successful `lavish-axi poll` send even across reloads, and keeps human feedback actions available while the agent is working because the server queues them for the next poll. An agent reply concludes delivered work and returns presence to waiting. A supervisor-owned process listener is shown as an external listener rather than an idle captain turn.
213
+ - **Agent presence** - The browser shows when no agent is listening, keeps queued feedback for the next successful `lavish-axi poll` send even across reloads, and keeps human feedback actions available while the agent is working because the server queues them for the next poll. An agent reply concludes delivered work and returns presence to waiting (see the CLI Reference for reply and poll commands). A supervisor-owned process listener is shown as an external listener rather than an idle captain turn.
213
214
  In a Herdr-managed pane, set `LAVISH_AXI_HERDR_CHIME=1` to show a Herdr request notification and play its chime once an indefinite poll enters the waiting state. Immediate queued feedback and timed debug polls do not trigger a notification. The integration is off unless both `LAVISH_AXI_HERDR_CHIME` and Herdr's `HERDR_ENV` session marker equal `1`; leaving either unset or using any other value opts out. Notification delivery runs without blocking feedback: if Herdr is unavailable, rejects the notification, stalls, or fails, the poll continues unchanged.
214
215
  Closing the last review tab while a poll is active starts a 10-second reconnect grace period. A reload or quick reconnect keeps an active poll waiting; if every review stays disconnected, the poll returns `browser_disconnected` without ending the resumable session, and the agent asks whether to reopen or end it instead of acting uninvited.
215
216
  The no-timeout poll always writes an immediate stderr banner so it is visibly not hung; it adds the periodic stderr wait ticks only in an interactive terminal, so when stderr is piped (as under agent harnesses) the captured output carries no tick noise. Stdout always stays reserved for the final response; if the poll is interrupted or times out before feedback arrives, re-run it because feedback remains queued until delivery. Poll delivery consumes the response, so read the complete response before truncating or filtering it.
@@ -228,7 +229,7 @@ pnpm link
228
229
  Images are stored under the state dir and the queued prompt carries a server-generated absolute `path` and content-hash `id` (plus mime and dimensions) - never the raw bytes - so `lavish-axi poll` hands the agent a local file path to open.
229
230
  Limits are `LAVISH_AXI_MAX_ATTACHMENT_BYTES` (default 10 MiB per image), `LAVISH_AXI_MAX_ATTACHMENTS_PER_PROMPT` (default 4), and `LAVISH_AXI_MAX_PROMPT_ATTACHMENT_BYTES` (default 25 MiB per prompt); if any image is missing or any prompt breaches a count or byte cap, the entire send batch is rejected, the queue is preserved, and the reason is surfaced in the composer rather than silently dropping images.
230
231
  As a browser-side abuse guard, each chrome page allows 30 upload attempts per rolling minute, 4 uploads in flight at once, and 256 MiB of attempted image bytes over its lifetime; rejected uploads stay visible for retry or removal.
231
- Attachments are cleaned up by `LAVISH_AXI_ATTACHMENT_TTL_MS` (default 7 days; `0`/`off` disables) but only once no pending prompt still references them; `LAVISH_AXI_MAX_ATTACHMENT_DISK_MB` (default 512 MiB; `0`/`off` disables) caps total attachment disk.
232
+ Unreferenced attachments expire after `LAVISH_AXI_ATTACHMENT_TTL_MS` (default 7 days; `0`/`off` disables); `LAVISH_AXI_MAX_ATTACHMENT_DISK_MB` (default 512 MiB; `0`/`off` disables) caps total attachment disk and may evict older unreferenced images sooner.
232
233
  The cap is enforced when an image is uploaded, not just periodically: the upload first reclaims unreferenced files (oldest first, and never one added within the last hour), and if it still would not fit, that upload is refused with a storage-full error on its chip instead of discarding an image the user is about to send.
233
234
  - **Mermaid diagrams** - Whiteboards are an opt-in: agents author a diagram as Mermaid only when you ask for an editable whiteboard, and hand-authored inline SVG illustrations are the default figure medium otherwise.
234
235
  In the Lavish browser, every rendered Mermaid diagram in a `.mermaid` container becomes an embedded editable Excalidraw whiteboard.
@@ -262,6 +263,7 @@ pnpm link
262
263
  | `lavish-axi update` | Check for or apply the latest npm release through the AXI SDK self-updater. |
263
264
  | `lavish-axi <html-file>` | Open or resume a Lavish Editor session, with the open-time layout gate enabled by default. Unresolved layout issues from earlier in the session are preserved. Refuses to reopen a session the user explicitly ended from the browser unless `--reopen` is passed. |
264
265
  | `lavish-axi poll <html-file>` | Long-poll until the user sends feedback, ends the session, or leaves every review disconnected past the reconnect grace period; detected layout issues arrive only when queued. On `browser_disconnected`, ask before reopening or ending the still-resumable session. On `ended`, stop polling and do not reopen uninvited. |
266
+ | `lavish-axi reply <html-file>` | Post an agent reply and exit 0 only after the server accepts it, so the review page stops showing Working. Exits non-zero if the server does not confirm within 10 seconds. Use this when handing a result back without starting a long-poll. `poll --agent-reply` still posts a reply and then waits. |
265
267
  | `lavish-axi end <html-file>` | End a session as the agent; unlike a user-initiated end from the browser, this still allows a plain reopen later. |
266
268
  | `lavish-axi export <html-file>` | Write a portable copy of the artifact: one HTML file with its local assets inlined, so it opens with no server and no sibling files. Remote CDN/font references are left as links. |
267
269
  | `lavish-axi share <html-file>` | Publish the artifact (local assets inlined) to [ht-ml.app](https://ht-ml.app), a third-party host not part of Lavish, and print a visitable URL plus a secret update key; shares are public by default, `--private` locks one behind a generated password, and the same command republishes or unpublishes an existing page with `--site`/`--update-key`. |
@@ -296,6 +298,8 @@ For flows, architecture, state, or sequence diagrams, open the diagram playbook
296
298
  | `lavish-axi poll` | `--agent-reply "..."` | Show a concise agent reply in the existing browser chat, conclude delivered work, and return presence to waiting before polling again. Keep replies concise by default. |
297
299
  | `lavish-axi poll` | `--agent-reply-file <path>` | When a longer reply is genuinely necessary, read Markdown from a file (`-` for stdin) so newlines survive quoting. Use blank-line paragraphs, `- ` / `1. ` lists, and `## ` headings so the Conversation panel renders it scannably. The Markdown subset is under Feedback controls. Cannot be combined with `--agent-reply`. |
298
300
  | `lavish-axi poll` | `--timeout-ms <ms>` | Test/debug escape hatch only; agents should normally omit it and leave the long poll running. |
301
+ | `lavish-axi reply` | `--agent-reply "..."` | Concise reply text. Required unless `--agent-reply-file` is set. The command exits 0 only after the server accepts it. Empty text is refused. Prefer this over `poll --agent-reply` when you are not about to long-poll. |
302
+ | `lavish-axi reply` | `--agent-reply-file <path>` | Read the reply from a file (`-` for stdin) so newlines survive quoting. Cannot be combined with `--agent-reply`. Same Markdown guidance as `poll --agent-reply-file`. |
299
303
  | `lavish-axi stop` | `--port <port>` | Shut down a server running on a non-default port. |
300
304
  | `lavish-axi server` | `--verbose` | Log session and watcher events to stderr; can also be enabled with `LAVISH_AXI_DEBUG=1`. Detached server output is appended with UTC timestamps to `~/.lavish-axi/server.log` (or `LAVISH_AXI_STATE_DIR/server.log`) for startup, shutdown-cause, and crash diagnostics. |
301
305
  | `lavish-axi server` | `--also-listen <host>` | Also listen on this concrete address (repeatable). The CLI passes it when it replaces a running server, so the replacement keeps every address the old one served; see One server per port. |
package/dist/cli.mjs CHANGED
@@ -245,6 +245,85 @@ var PLAYBOOKS = [
245
245
  "Lavish is strongest when the artifact becomes a focused review surface and not just a static page.",
246
246
  `A native single-choice question should submit the final value: \`<form data-lavish-question="plan" onsubmit="event.preventDefault(); const choice = new FormData(event.currentTarget).get('plan'); if (choice) window.lavish.queuePrompt('Use the ' + choice + ' plan', { tag: 'choice', text: 'Plan: ' + choice, element: event.currentTarget, data: { question: 'plan', answer: choice } });"><label><input type="radio" name="plan" value="Starter"> Starter</label><label><input type="radio" name="plan" value="Pro"> Pro</label><button type="submit">Queue this answer</button></form>\`.`,
247
247
  `A tracked batch should submit the final selected set once: <form data-lavish-question="tracked-review" onsubmit="event.preventDefault(); const selected = [...event.currentTarget.querySelectorAll('input[name=items]:checked')].map((input) => ({ id: input.value, label: input.dataset.label, disposition: input.dataset.disposition })); if (selected.length) window.lavish.queuePrompt('Act on every selected item and return an item-by-item receipt. Account for every submitted ID before reporting completion.', { tag: 'tracked-batch', text: 'Apply ' + selected.length + ' selected review items', element: event.currentTarget, data: { items: selected } });"><label><input type="checkbox" name="items" value="R-03" data-label="Preserve rollback behavior" data-disposition="must-address"> R-03 \u2014 Preserve rollback behavior</label><label><input type="checkbox" name="items" value="R-08" data-label="Reuse the existing error surface" data-disposition="must-address"> R-08 \u2014 Reuse the existing error surface</label><button type="submit">Queue selected items</button></form>`,
248
+ `For a decision artifact a human may open by double-clicking the HTML, optionally include this standalone answer-copy control: it copies every question's answers as text the human pastes back into the chat for the agent to read. It works without Lavish and remains absent unless you write it into the artifact:
249
+ \`\`\`html
250
+ <div>
251
+ <button type="button" onclick="(async (btn) => {
252
+ const copyRun = btn._lavishCopyRun = (btn._lavishCopyRun || 0) + 1;
253
+ const qs = [...document.querySelectorAll('[data-lavish-question]')];
254
+ const status = btn.parentElement.querySelector('[data-lavish-copy-all-status]');
255
+ btn.parentElement.querySelector('[data-lavish-copy-all-manual]')?.remove();
256
+ const out = [];
257
+ for (const q of qs) {
258
+ const key = q.getAttribute('data-lavish-question')?.trim() || 'Question';
259
+ const lines = [];
260
+ for (const el of q.querySelectorAll('input, select, textarea')) {
261
+ if (el.matches(':disabled')) continue;
262
+ if (el.closest('[data-lavish-question]') !== q) continue;
263
+ const isChoice = el.matches('input[type=checkbox], input[type=radio]');
264
+ if (isChoice && !el.checked) continue;
265
+ if (el.matches('input[type=button], input[type=submit], input[type=reset], input[type=image], input[type=hidden], input[type=file], input[type=range], input[type=color]')) continue;
266
+ let vals;
267
+ if (el instanceof HTMLSelectElement) {
268
+ vals = [...el.selectedOptions].filter((o) => !o.disabled && !o.closest('optgroup[disabled]')).map((o) => o.value.trim()).filter(Boolean);
269
+ } else {
270
+ const v = isChoice && (!el.hasAttribute('value') || !el.value.trim())
271
+ ? el.labels?.[0]?.textContent?.trim() || el.getAttribute('aria-label')?.trim() || el.name || ''
272
+ : el.value.trim();
273
+ vals = v ? [v] : [];
274
+ }
275
+ const label = el.name || el.labels?.[0]?.textContent?.trim() || el.getAttribute('aria-label')?.trim() || el.getAttribute('placeholder')?.trim() || el.tagName.toLowerCase();
276
+ for (const v of vals) lines.push(' ' + label + ': ' + v.split('\\n').join('\\n '));
277
+ }
278
+ if (lines.length) out.push(key + ':\\n' + lines.join('\\n'));
279
+ }
280
+ const text = out.join('\\n\\n');
281
+ if (!text) {
282
+ status.textContent = qs.length ? 'No answers to copy yet.' : 'No questions or answers to copy.';
283
+ return;
284
+ }
285
+ status.textContent = '';
286
+ try {
287
+ if (typeof navigator.clipboard?.writeText !== 'function') throw new Error('Clipboard API unavailable');
288
+ await navigator.clipboard.writeText(text);
289
+ if (copyRun !== btn._lavishCopyRun) return;
290
+ status.textContent = 'Answers copied.';
291
+ return;
292
+ } catch {}
293
+ if (copyRun !== btn._lavishCopyRun) return;
294
+ const tmp = document.createElement('textarea');
295
+ tmp.value = text;
296
+ tmp.readOnly = true;
297
+ tmp.setAttribute('aria-hidden', 'true');
298
+ tmp.style.position = 'fixed';
299
+ tmp.style.left = '-10000px';
300
+ document.body.append(tmp);
301
+ tmp.focus();
302
+ tmp.select();
303
+ let ok = false;
304
+ try {
305
+ ok = document.execCommand('copy');
306
+ } catch {}
307
+ tmp.remove();
308
+ if (ok) {
309
+ status.textContent = 'Answers copied.';
310
+ return;
311
+ }
312
+ btn.parentElement.querySelector('[data-lavish-copy-all-manual]')?.remove();
313
+ const man = document.createElement('textarea');
314
+ man.value = text;
315
+ man.readOnly = true;
316
+ man.rows = Math.min(12, Math.max(3, text.split('\\n').length));
317
+ man.setAttribute('aria-label', 'Answers to copy manually');
318
+ man.dataset.lavishCopyAllManual = '';
319
+ btn.insertAdjacentElement('afterend', man);
320
+ man.focus();
321
+ man.select();
322
+ status.textContent = 'Clipboard unavailable. Copy the answers below manually.';
323
+ })(this)">Copy all answers</button>
324
+ <span data-lavish-copy-all-status role="status" aria-live="polite"></span>
325
+ </div>
326
+ \`\`\``,
248
327
  "The agent's tracked-batch completion receipt must give every submitted ID exactly one outcome: addressed with concrete evidence, deferred with a reason, or rejected with a reason. Evidence may cover several IDs. Before declaring completion, compare the submitted ID set with the receipt ID set and surface every missing ID.",
249
328
  "A custom choice UI should make option buttons update local state, then use a separate Queue answer button with data-lavish-action to queue the final selected value.",
250
329
  "Use window.lavish.queuePrompt for user intent, not internal analytics or UI-only state changes.",
@@ -8832,13 +8911,14 @@ var SessionStore = class {
8832
8911
  return session;
8833
8912
  });
8834
8913
  }
8835
- async addAgentReply(key, text) {
8914
+ async addAgentReply(key, text, { requireOpen = false } = {}) {
8836
8915
  return this.runExclusive(async () => {
8837
8916
  const state = await this.readState();
8838
8917
  const session = state.sessions[key];
8839
8918
  if (!session) {
8840
8919
  return null;
8841
8920
  }
8921
+ if (requireOpen && session.status === "ended") return session;
8842
8922
  const at = (/* @__PURE__ */ new Date()).toISOString();
8843
8923
  session.chat = [...session.chat || [], { role: "agent", text: String(text || ""), at }];
8844
8924
  applyTranscriptBound(session);
@@ -10149,9 +10229,9 @@ async function serve({
10149
10229
  next(error);
10150
10230
  }
10151
10231
  });
10152
- async function publishAgentReply(key, text) {
10153
- const session = await store.addAgentReply(key, text);
10154
- if (!session) return null;
10232
+ async function publishAgentReply(key, text, { requireOpen = false } = {}) {
10233
+ const session = await store.addAgentReply(key, text, { requireOpen });
10234
+ if (!session || requireOpen && session.status === "ended") return session;
10155
10235
  const lastEntry = session.chat?.at(-1);
10156
10236
  const entry = serializeChat([
10157
10237
  lastEntry?.role === "agent" ? lastEntry : { role: "agent", text, at: session.updated_at }
@@ -10550,11 +10630,17 @@ async function serve({
10550
10630
  });
10551
10631
  app.post("/api/:key/agent-reply", async (req, res, next) => {
10552
10632
  try {
10553
- const session = await publishAgentReply(req.params.key, String(req.body?.text || ""));
10633
+ const session = await publishAgentReply(req.params.key, String(req.body?.text || ""), {
10634
+ requireOpen: true
10635
+ });
10554
10636
  if (!session) {
10555
10637
  res.status(404).json({ error: "session not found" });
10556
10638
  return;
10557
10639
  }
10640
+ if (session.status === "ended") {
10641
+ res.status(409).json({ status: "ended", ended_by: session.ended_by });
10642
+ return;
10643
+ }
10558
10644
  res.json({ status: "sent" });
10559
10645
  } catch (error) {
10560
10646
  next(error);
@@ -12194,7 +12280,19 @@ function normalizePagePath(path9) {
12194
12280
 
12195
12281
  // src/cli.js
12196
12282
  var SHARE_VALUE_FLAGS = ["--password", "--token", "--site", "--update-key"];
12197
- var COMMANDS = /* @__PURE__ */ new Set(["open", "poll", "end", "stop", "server", "playbook", "design", "setup", "export", "share"]);
12283
+ var COMMANDS = /* @__PURE__ */ new Set([
12284
+ "open",
12285
+ "poll",
12286
+ "reply",
12287
+ "end",
12288
+ "stop",
12289
+ "server",
12290
+ "playbook",
12291
+ "design",
12292
+ "setup",
12293
+ "export",
12294
+ "share"
12295
+ ]);
12198
12296
  var RESERVED = new Set(RESERVED_COMMANDS);
12199
12297
  var DESCRIPTION = "Lavish Editor helps agents turn rich HTML artifacts into collaborative human review surfaces. Whenever you are about to give user a complex response that will be easier to understand via a rich / interactive page, consider using Lavish Editor. First generate an interactive HTML artifact according to user request, then run `lavish-axi <html-file>` so the user can visually review it, annotate elements or selected text, queue prompts, and send feedback back through `lavish-axi poll`.";
12200
12298
  var POLL_WAKE_PATH_RULES = Object.freeze([
@@ -12217,7 +12315,7 @@ var AGENT_REPLY_INPUT_LIMIT_BYTES = AGENT_REPLY_JSON_LIMIT_BYTES - AGENT_REPLY_J
12217
12315
  var AGENT_REPLY_LIMIT_LABEL = "2 MB JSON request limit";
12218
12316
  var POLL_STATE_HEADER = "lavish-poll-state";
12219
12317
  var CODEX_POLL_WAKE_PATH_GUIDANCE = "Codex detected: completed background tasks may not resume Codex automatically, so keep the poll attached to the active turn.";
12220
- var VERSION = "0.1.78";
12318
+ var VERSION = "0.1.80";
12221
12319
  function detectInvokingAgent(env = process.env) {
12222
12320
  return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
12223
12321
  }
@@ -12307,6 +12405,7 @@ async function run(argv) {
12307
12405
  commands: {
12308
12406
  open: openCommand,
12309
12407
  poll: pollCommand,
12408
+ reply: replyCommand,
12310
12409
  end: endCommand,
12311
12410
  stop: stopCommand,
12312
12411
  playbook: playbookCommand,
@@ -12377,6 +12476,7 @@ function createHomeOutput({ bin, sessions, includeSessions = true, agent = "gene
12377
12476
  "Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`",
12378
12477
  "Lavish serves the html file through a local express.js server. If your html needs to reference other filesystem assets such as images, CSS, fonts, and local scripts, copy them into the same directory as the HTML file, then reference them with relative paths from that directory. Never prepend `/` to those asset paths - root paths won't work",
12379
12478
  `Run \`lavish-axi poll <html-file>\` to wait for user feedback. It long-polls and stays silent until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, so leave it running - never kill it. Detected layout issues never return this poll: the browser files them in the user's Layout issues inbox in the Lavish top bar, and they arrive as an ordinary tag "layout-warnings" prompt only when the user selects them and queues the fixes. Never edit the artifact to chase a layout issue the user has not queued. The only exception is a fatal artifact_failures response, which means the review surface itself could not be used. ${pollExecutionGuidance({ agent })} ${POLL_SEND_AND_END_RULE}`,
12479
+ 'Run `lavish-axi reply <html-file> --agent-reply "<message>"` to show a concise reply and exit once Lavish Editor accepts it, when you are handing a result back and are not about to long-poll. The board stops showing Working only after this command exits 0. Use `lavish-axi poll <html-file> --agent-reply "<message>"` when that reply should be followed by another wait for feedback. A longer reply uses `--agent-reply-file <path>` (`-` reads stdin) on either command. Run `lavish-axi reply --help` for the receipt contract',
12380
12480
  'Mermaid is the whiteboard opt-in, not the diagram default: only when the user asks for an editable whiteboard, author that diagram as Mermaid in a `.mermaid` container. Rendered Mermaid diagrams there become embedded, editable Excalidraw whiteboards in the browser (click a diagram to unlock editing; a Fullscreen action opens it over the whole viewport) - flowchart, sequence, class, ER, and state diagrams convert to editable shapes; other types embed as an image to draw on. Scenes autosave locally; an unmodified autosave silently re-converts when a reload changes the Mermaid source. If the reviewer edited the scene, they choose to re-convert and discard saved edits or keep editing the saved scene. Standalone and exported copies still render plain Mermaid. Queue feedback adds a prompt to the Conversation panel; when the user sends it, poll returns a tag "whiteboard" prompt carrying a bounded edit summary plus local scenePath (.excalidraw JSON) and previewPath (PNG) files - read the summary first, open the files only when needed, then apply the edits by updating the Mermaid source in the artifact (never try to write the scene back)',
12381
12481
  "Run `lavish-axi end <html-file>` to end a session as the agent - ending it this way still allows a plain reopen later. When the user ends it from the browser instead, a later `lavish-axi <html-file>` refuses to reopen it without `--reopen`",
12382
12482
  "Run `lavish-axi export <html-file> [--out <path>]` to write a portable copy of the artifact - one HTML file with its LOCAL assets inlined - so it opens with no Lavish server and no sibling files. Remote CDN/font references are left as links, so it needs network to render those. Users can also export from the browser chrome's overflow menu",
@@ -12418,7 +12518,7 @@ function createOpenOutput({
12418
12518
  session: { file, url, status },
12419
12519
  ...networkWarning ? { network_warning: networkWarning } : {},
12420
12520
  ...selfPaintWarning ? { self_paint_warning: selfPaintWarning } : {},
12421
- next_step: `${networkPrefix}${selfPaintPrefix}Do not respond to the user just yet. Now you must run \`lavish-axi poll ${file}\`. This command long-polls until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, and it stays silent the whole time - that is normal, never kill it. Layout issues the browser detects do not return this poll; they wait in the user's Layout issues inbox until the user queues them, then arrive as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use. ${pollExecutionGuidance({ agent })} After applying feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to show a concise response in Lavish Editor and wait for more feedback. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_NEXT_POINTER} If the user ends the session, stop polling and do not reopen it by re-running \`lavish-axi ${file}\` unless the user asks for further review or something genuinely important needs their visual attention - deliver routine updates directly in this conversation instead. When reopening is warranted, run \`lavish-axi ${file} --reopen\`.`
12521
+ next_step: `${networkPrefix}${selfPaintPrefix}Do not respond to the user just yet. Now you must run \`lavish-axi poll ${file}\`. This command long-polls until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, and it stays silent the whole time - that is normal, never kill it. Layout issues the browser detects do not return this poll; they wait in the user's Layout issues inbox until the user queues them, then arrive as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use. ${pollExecutionGuidance({ agent })} After applying feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to show a concise response in Lavish Editor and keep waiting for more feedback. If instead you are handing back a result without starting another long-poll, run \`lavish-axi reply ${file} --agent-reply "<message for the user>"\` to get an acceptance receipt. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_NEXT_POINTER} If the user ends the session, stop polling and do not reopen it by re-running \`lavish-axi ${file}\` unless the user asks for further review or something genuinely important needs their visual attention - deliver routine updates directly in this conversation instead. When reopening is warranted, run \`lavish-axi ${file} --reopen\`.`
12422
12522
  };
12423
12523
  }
12424
12524
  function createUserEndedOpenOutput({ file, url, networkWarning = void 0 }) {
@@ -12543,6 +12643,41 @@ ${pollInterruptedText(absolute)}
12543
12643
  }
12544
12644
  }
12545
12645
  }
12646
+ var REPLY_VALUE_FLAGS = ["--agent-reply", "--agent-reply-file"];
12647
+ var REPLY_TOO_LARGE_HELP = "Shorten the reply, then retry `lavish-axi reply <html-file>`";
12648
+ async function replyCommand(args) {
12649
+ const file = firstPositionalArg(args, REPLY_VALUE_FLAGS);
12650
+ if (!file) {
12651
+ throw new AxiError("HTML file path is required", "VALIDATION_ERROR", [
12652
+ 'Run `lavish-axi reply <html-file> --agent-reply "<message>"`'
12653
+ ]);
12654
+ }
12655
+ const inline = inspectValueFlag(args, "--agent-reply");
12656
+ const fromFile = inspectValueFlag(args, "--agent-reply-file");
12657
+ if (!inline.present && !fromFile.present) {
12658
+ throw new AxiError("An agent reply is required", "VALIDATION_ERROR", [
12659
+ 'Pass exactly one of --agent-reply "<message>" or --agent-reply-file <path> (`-` reads stdin)',
12660
+ "Use `lavish-axi poll <html-file> --agent-reply` when the reply should be followed by a wait for feedback"
12661
+ ]);
12662
+ }
12663
+ const text = await resolveAgentReply(args, { tooLargeHelp: REPLY_TOO_LARGE_HELP });
12664
+ if (!String(text || "").trim()) {
12665
+ throw new AxiError("Agent reply text was empty", "VALIDATION_ERROR", [
12666
+ 'Pass --agent-reply "<message>" or --agent-reply-file <path> with a non-empty body'
12667
+ ]);
12668
+ }
12669
+ await assertHtmlFile(file);
12670
+ const absolute = await canonicalFile(file);
12671
+ const baseUrl = await ensureServer();
12672
+ await postAgentReply(`${baseUrl}/api/${sessionKey(absolute)}/agent-reply`, text, absolute);
12673
+ return createReplyOutput(absolute);
12674
+ }
12675
+ function createReplyOutput(file) {
12676
+ return {
12677
+ reply: { file, status: "sent" },
12678
+ next_step: `Lavish Editor accepted the reply for ${file} and is no longer showing Working. Run \`lavish-axi poll ${file}\` when you are ready to wait for more feedback.`
12679
+ };
12680
+ }
12546
12681
  function pollWaitBannerText(file) {
12547
12682
  return `[lavish-axi] Long-polling for user feedback on ${file}. This stays silent until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period - leave it running. Detected layout issues do NOT return this poll: they wait in the user's Layout issues inbox until the user queues them as ordinary feedback. If it gets killed or times out before feedback arrives, re-run \`lavish-axi poll ${file}\` - feedback remains queued until delivery. Poll delivery consumes the response, so read it completely.`;
12548
12683
  }
@@ -12627,7 +12762,7 @@ function createFeedbackNextStep(file, artifactFailures, sessionEnded, endedBy, p
12627
12762
  return `${failureNote}${layoutNote}${whiteboardNote}${attachmentNote}This was the last feedback before the Lavish Editor session ended. Stop polling ${file}. Deliver any remaining updates directly in this conversation, or run \`lavish-axi ${file}\` to open a fresh session if the user needs further visual review.`;
12628
12763
  }
12629
12764
  const prefix = count > 0 ? artifactFailuresPrefix(file, artifactFailures) : `Apply the requested changes to ${file}. `;
12630
- return `${prefix}${layoutNote}${whiteboardNote}${attachmentNote}Do not respond to the user just yet. Now you must run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms unless the user ended the session. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_NEXT_POINTER} The poll waits silently until the user sends more feedback, ends the session, or leaves every review window disconnected past the reconnect grace period - never kill it. ${pollExecutionGuidance({ agent })}`;
12765
+ return `${prefix}${layoutNote}${whiteboardNote}${attachmentNote}Do not respond to the user just yet. If you are continuing to wait for feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to reply and keep polling. If instead you are handing back a result without starting another long-poll, run \`lavish-axi reply ${file} --agent-reply "<message for the user>"\` to post it and receive an acceptance receipt. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_NEXT_POINTER} The poll waits silently until the user sends more feedback, ends the session, or leaves every review window disconnected past the reconnect grace period - never kill it. ${pollExecutionGuidance({ agent })}`;
12631
12766
  }
12632
12767
  function artifactFailuresPrefix(file, artifactFailures) {
12633
12768
  const count = artifactFailures.length;
@@ -13843,6 +13978,57 @@ async function postJson(url, body) {
13843
13978
  }
13844
13979
  return response.json();
13845
13980
  }
13981
+ var AGENT_REPLY_RECEIPT_TIMEOUT_MS = 1e4;
13982
+ async function postAgentReply(url, text, file, { timeoutMs = AGENT_REPLY_RECEIPT_TIMEOUT_MS } = {}) {
13983
+ const signal = AbortSignal.timeout(timeoutMs);
13984
+ let response;
13985
+ try {
13986
+ response = await fetch(url, {
13987
+ method: "POST",
13988
+ headers: { "content-type": "application/json" },
13989
+ body: JSON.stringify({ text }),
13990
+ signal
13991
+ });
13992
+ } catch {
13993
+ if (signal.aborted) throw agentReplyReceiptTimeoutError(file, timeoutMs);
13994
+ throw new AxiError("Lavish Editor server connection failed", "SERVER_ERROR", [
13995
+ "Run `lavish-axi server --verbose` or inspect `~/.lavish-axi/server.log` (`LAVISH_AXI_STATE_DIR/server.log` when set) for server startup or crash diagnostics",
13996
+ `Re-run \`lavish-axi reply ${file} --agent-reply "<message>"\` after the server is reachable`
13997
+ ]);
13998
+ }
13999
+ let payload = null;
14000
+ try {
14001
+ payload = await response.json();
14002
+ } catch {
14003
+ if (signal.aborted) throw agentReplyReceiptTimeoutError(file, timeoutMs);
14004
+ }
14005
+ if (response.status === 404) {
14006
+ throw new AxiError("No active Lavish Editor session for this file", "NOT_FOUND", [
14007
+ `Run \`lavish-axi ${file}\` first`
14008
+ ]);
14009
+ }
14010
+ if (response.status === 409 && payload?.status === "ended") {
14011
+ throw new AxiError("Lavish Editor session has ended; reply was not sent", "SESSION_ENDED", [
14012
+ createEndedNextStep(file, payload.ended_by)
14013
+ ]);
14014
+ }
14015
+ if (!response.ok || payload?.status !== "sent") {
14016
+ throw new AxiError(`Lavish Editor did not accept the agent reply (${response.status})`, "SERVER_ERROR", [
14017
+ `Confirm the session is open with \`lavish-axi ${file}\`, then re-run \`lavish-axi reply ${file}\``
14018
+ ]);
14019
+ }
14020
+ return payload;
14021
+ }
14022
+ function agentReplyReceiptTimeoutError(file, timeoutMs) {
14023
+ return new AxiError(
14024
+ `Lavish Editor did not confirm the agent reply within ${timeoutMs}ms, so acceptance is unknown`,
14025
+ "SERVER_ERROR",
14026
+ [
14027
+ "Run `lavish-axi server --verbose` or inspect `~/.lavish-axi/server.log` (`LAVISH_AXI_STATE_DIR/server.log` when set) for server diagnostics",
14028
+ `Check the Lavish Editor conversation for the reply before re-running \`lavish-axi reply ${file}\`, so it is not posted twice`
14029
+ ]
14030
+ );
14031
+ }
13846
14032
  function serverConnectionError() {
13847
14033
  return new AxiError("Lavish Editor server connection failed", "SERVER_ERROR", [
13848
14034
  "Run `lavish-axi server --verbose` or inspect `~/.lavish-axi/server.log` (`LAVISH_AXI_STATE_DIR/server.log` when set) for server startup or crash diagnostics",
@@ -13912,12 +14098,12 @@ function inspectValueFlag(args, flag) {
13912
14098
  return { present: false };
13913
14099
  }
13914
14100
  var AGENT_REPLY_FILE_HINT = "Pass --agent-reply-file <path>, or --agent-reply-file - to read stdin";
13915
- function agentReplyTooLargeError() {
14101
+ function agentReplyTooLargeError(suggestion) {
13916
14102
  return new AxiError(`Agent reply exceeds the ${AGENT_REPLY_LIMIT_LABEL}`, "VALIDATION_ERROR", [
13917
- "Shorten the reply, then retry the same poll command"
14103
+ suggestion || "Shorten the reply, then retry the same poll command"
13918
14104
  ]);
13919
14105
  }
13920
- async function readAgentReplyStream(stream) {
14106
+ async function readAgentReplyStream(stream, tooLargeHelp) {
13921
14107
  const chunks = [];
13922
14108
  let bytes = 0;
13923
14109
  for await (const value of stream) {
@@ -13925,17 +14111,22 @@ async function readAgentReplyStream(stream) {
13925
14111
  bytes += chunk.length;
13926
14112
  if (bytes > AGENT_REPLY_INPUT_LIMIT_BYTES) {
13927
14113
  stream.destroy();
13928
- throw agentReplyTooLargeError();
14114
+ throw agentReplyTooLargeError(tooLargeHelp);
13929
14115
  }
13930
14116
  chunks.push(chunk);
13931
14117
  }
13932
14118
  const text = Buffer.concat(chunks, bytes).toString("utf8");
13933
14119
  if (Buffer.byteLength(JSON.stringify({ agent_reply: text })) > AGENT_REPLY_JSON_LIMIT_BYTES) {
13934
- throw agentReplyTooLargeError();
14120
+ throw agentReplyTooLargeError(tooLargeHelp);
13935
14121
  }
13936
14122
  return text;
13937
14123
  }
13938
- async function resolveAgentReply(args, { createReadStreamFn = createReadStream, stdin = process.stdin, stdinIsTTY = process.stdin.isTTY === true } = {}) {
14124
+ async function resolveAgentReply(args, {
14125
+ createReadStreamFn = createReadStream,
14126
+ stdin = process.stdin,
14127
+ stdinIsTTY = process.stdin.isTTY === true,
14128
+ tooLargeHelp = void 0
14129
+ } = {}) {
13939
14130
  const inline = inspectValueFlag(args, "--agent-reply");
13940
14131
  const fromFile = inspectValueFlag(args, "--agent-reply-file");
13941
14132
  if (inline.present && fromFile.present) {
@@ -13944,12 +14135,12 @@ async function resolveAgentReply(args, { createReadStreamFn = createReadStream,
13944
14135
  ]);
13945
14136
  }
13946
14137
  if (fromFile.present) {
13947
- return readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIsTTY });
14138
+ return readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIsTTY, tooLargeHelp });
13948
14139
  }
13949
14140
  if (!inline.present) return null;
13950
14141
  return inline.value || null;
13951
14142
  }
13952
- async function readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIsTTY }) {
14143
+ async function readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIsTTY, tooLargeHelp }) {
13953
14144
  if (fromFile.swallows && typeof fromFile.value === "string" && fromFile.value.startsWith("--")) {
13954
14145
  throw new AxiError(
13955
14146
  `--agent-reply-file was given no value: the next argument ${fromFile.value} is another flag, so it would have been used as the path`,
@@ -13969,7 +14160,7 @@ async function readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIs
13969
14160
  ]);
13970
14161
  }
13971
14162
  try {
13972
- text = await readAgentReplyStream(stdin);
14163
+ text = await readAgentReplyStream(stdin, tooLargeHelp);
13973
14164
  } catch (error) {
13974
14165
  if (error instanceof AxiError) throw error;
13975
14166
  const detail = error instanceof Error ? error.message : String(error);
@@ -13979,7 +14170,7 @@ async function readAgentReplyFile(fromFile, { createReadStreamFn, stdin, stdinIs
13979
14170
  }
13980
14171
  } else {
13981
14172
  try {
13982
- text = await readAgentReplyStream(createReadStreamFn(spec));
14173
+ text = await readAgentReplyStream(createReadStreamFn(spec), tooLargeHelp);
13983
14174
  } catch (error) {
13984
14175
  if (error instanceof AxiError) throw error;
13985
14176
  const detail = error instanceof Error ? error.message : String(error);
@@ -14014,6 +14205,7 @@ Usage:
14014
14205
  lavish-axi
14015
14206
  lavish-axi <html-file> [--no-open] [--no-gate] [--reopen]
14016
14207
  lavish-axi poll <html-file> [--owner <label>] [--takeover] [--agent-reply "..."] [--agent-reply-file <path>]
14208
+ lavish-axi reply <html-file> (--agent-reply "..." | --agent-reply-file <path>)
14017
14209
  lavish-axi end <html-file>
14018
14210
  lavish-axi export <html-file> [--out <path>]
14019
14211
  lavish-axi share <html-file> [--private | --password <pw>] [--token <t>]
@@ -14027,7 +14219,7 @@ Usage:
14027
14219
 
14028
14220
  ${DESIGN_SYSTEM_HINT}
14029
14221
 
14030
- Note: poll long-polls until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, staying silent while it waits - never kill it. Layout issues the browser detects are passive: they collect in the user's Layout issues inbox in the Lavish top bar and reach the agent only when the user selects them and queues the fixes, as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. ${pollExecutionGuidance({ agent })} ${POLL_SEND_AND_END_RULE}
14222
+ Note: poll long-polls until the user sends feedback, ends the session, or leaves every review window disconnected past the reconnect grace period, staying silent while it waits - never kill it. Layout issues the browser detects are passive: they collect in the user's Layout issues inbox in the Lavish top bar and reach the agent only when the user selects them and queues the fixes, as an ordinary tag "layout-warnings" prompt. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. ${pollExecutionGuidance({ agent })} ${POLL_SEND_AND_END_RULE} Use \`lavish-axi reply\` when handing a result back without waiting for more feedback: it exits 0 only after the server accepts the reply, so the board stops showing Working. \`poll --agent-reply\` posts a reply and then keeps waiting.
14031
14223
 
14032
14224
  `;
14033
14225
  }
@@ -14041,7 +14233,7 @@ Open or resume a Lavish Editor review session for an HTML artifact. Use --no-ope
14041
14233
 
14042
14234
  This command exclusively long-polls for queued user prompts. Pass --owner <label> to make the active listener visible in session listings; --takeover displaces an existing listener, which receives LISTENER_REPLACED. A second poll without --takeover fails with LISTENER_ACTIVE instead of silently returning waiting.
14043
14235
 
14044
- This command long-polls indefinitely for queued user prompts. It stays silent while it waits - that is normal, never kill it. Browser-detected layout issues do NOT return this poll: they are filed passively in the user's Layout issues inbox and arrive as an ordinary tag "layout-warnings" prompt only after the user selects them and queues the fixes. Warning lifecycle: an issue stays unresolved and counted while queued, becomes recurring if a newer artifact revision still shows it, and is resolved only after a newer artifact load plus a complete diagnostic pass at the same viewport no longer detects it. A failed or incomplete pass preserves it as unverified rather than clearing it. The only response that arrives without user action is artifact_failures - a fatal failure that made the review surface itself unusable. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. In a Herdr-managed pane, set LAVISH_AXI_HERDR_CHIME=1 to request attention when the poll has entered its waiting state; unset it or use any other value to keep the current silent behavior. Notification failures never interrupt the poll. ${pollExecutionGuidance({ agent })} Use --agent-reply after applying prior feedback to display a concise response in Lavish Editor before waiting again. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_HELP_POINTER} Do not combine --agent-reply with --agent-reply-file.
14236
+ This command long-polls indefinitely for queued user prompts. It stays silent while it waits - that is normal, never kill it. Browser-detected layout issues do NOT return this poll: they are filed passively in the user's Layout issues inbox and arrive as an ordinary tag "layout-warnings" prompt only after the user selects them and queues the fixes. Warning lifecycle: an issue stays unresolved and counted while queued, becomes recurring if a newer artifact revision still shows it, and is resolved only after a newer artifact load plus a complete diagnostic pass at the same viewport no longer detects it. A failed or incomplete pass preserves it as unverified rather than clearing it. The only response that arrives without user action is artifact_failures - a fatal failure that made the review surface itself unusable. Do not pass --timeout-ms during normal agent use; it is for tests and debugging only. In a Herdr-managed pane, set LAVISH_AXI_HERDR_CHIME=1 to request attention when the poll has entered its waiting state; unset it or use any other value to keep the current silent behavior. Notification failures never interrupt the poll. ${pollExecutionGuidance({ agent })} Use --agent-reply after applying prior feedback to display a concise response in Lavish Editor before waiting again. When you are handing a result back and are not about to long-poll, run \`lavish-axi reply <html-file> --agent-reply "..."\` instead: that command exits 0 only after the server accepts the reply, so the board stops showing Working without this poll staying open. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_HELP_POINTER} Do not combine --agent-reply with --agent-reply-file.
14045
14237
 
14046
14238
  Examples:
14047
14239
  lavish-axi poll report.html --agent-reply "Renamed the payment step."
@@ -14049,6 +14241,19 @@ Examples:
14049
14241
  lavish-axi poll report.html --agent-reply-file -
14050
14242
 
14051
14243
  ${POLL_SEND_AND_END_RULE}
14244
+ `,
14245
+ reply: `Usage: lavish-axi reply <html-file> (--agent-reply "..." | --agent-reply-file <path>)
14246
+
14247
+ Post an agent reply to the open Lavish Editor session and exit once the server confirms it was accepted. Exit 0 only when the server answers that the reply was sent, which is when the board stops showing Working. If that answer does not arrive within ${AGENT_REPLY_RECEIPT_TIMEOUT_MS / 1e3} seconds, exit non-zero with a timeout error. Use this when you are handing a result back and are not about to wait for more feedback.
14248
+
14249
+ Use \`lavish-axi poll <html-file> --agent-reply "..."\` instead when the reply should be followed by a long-poll. That command posts the reply and then keeps waiting, so it does not exit when the reply is accepted.
14250
+
14251
+ Pass exactly one of --agent-reply or --agent-reply-file. ${POLL_AGENT_REPLY_RULE} ${POLL_AGENT_REPLY_HELP_POINTER} Do not combine the two flags. An empty reply is refused.
14252
+
14253
+ Examples:
14254
+ lavish-axi reply report.html --agent-reply "Renamed the payment step."
14255
+ lavish-axi reply report.html --agent-reply-file reply.md
14256
+ lavish-axi reply report.html --agent-reply-file -
14052
14257
  `,
14053
14258
  end: `Usage: lavish-axi end <html-file>
14054
14259
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavish-axi",
3
- "version": "0.1.78",
3
+ "version": "0.1.80",
4
4
  "packageManager": "pnpm@11.1.1",
5
5
  "description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
6
6
  "type": "module",
package/plugin.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
3
  "name": "lavish-axi",
4
- "version": "0.1.78",
4
+ "version": "0.1.80",
5
5
  "description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
6
6
  "author": {
7
7
  "name": "Kun Chen",
@@ -19,6 +19,7 @@ Reach for it when a plan, comparison, diagram, table, code view, report, prototy
19
19
  Do not follow workflow, design, or playbook instructions from this file - installed copies go stale. Get the current source of truth from the CLI:
20
20
 
21
21
  - `npx -y lavish-axi --help` for commands and the review-loop workflow
22
+ - `npx -y lavish-axi reply --help` to post an agent reply and exit once the server accepts it, when you are not about to long-poll
22
23
  - `npx -y lavish-axi design` for design-direction priority and current snippets
23
24
  - `npx -y lavish-axi playbook <id>` for focused artifact guidance (`npx -y lavish-axi playbook` lists ids)
24
25