lavish-axi 0.1.56 → 0.1.58
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 +7 -5
- package/dist/chrome-client.js +19 -6
- package/dist/cli.mjs +74 -36
- package/package.json +1 -1
- package/plugin.json +1 -1
- package/skills/lavish/SKILL.md +6 -6
package/README.md
CHANGED
|
@@ -31,10 +31,10 @@ HTML is the new markdown. Lavish is the new editor for your HTML artifacts.
|
|
|
31
31
|
Agents are good at producing rich HTML artifacts, but the human-agent collaboration loop on such artifacts is lacking and falls back into screenshots and long responses for “tell me what to change.”
|
|
32
32
|
That loses the thing HTML is best at: interactivity.
|
|
33
33
|
|
|
34
|
-
Lavish Editor opens agent-generated HTML files in a local browser, lets you pinpoint elements and selected text, edit
|
|
34
|
+
Lavish Editor opens agent-generated HTML files in a local browser, lets you pinpoint elements and selected text, edit diagrams your agent authored as Mermaid whiteboards, and send feedback to the agent to address.
|
|
35
35
|
|
|
36
36
|
- **Local-first** - Review local HTML artifacts with a local CLI and no cloud dependency in the core feedback loop; hosted sharing through third-party ht-ml.app is explicit and opt-in.
|
|
37
|
-
- **Human-AI collaboration** - Annotate elements and selected text ranges, edit Mermaid diagrams
|
|
37
|
+
- **Human-AI collaboration** - Annotate elements and selected text ranges, edit Mermaid whiteboard diagrams, and send messages to the agent without leaving Lavish Editor.
|
|
38
38
|
- **Battery included** - Lavish Editor teaches your agent good visualization for common use cases such as product or technical plans, design explorations and more out of the box.
|
|
39
39
|
|
|
40
40
|
Lavish Editor is an [AXI](https://axi.md), which means -
|
|
@@ -162,7 +162,7 @@ pnpm link
|
|
|
162
162
|
- **Portable artifacts** - The artifact runs in a sandboxed iframe while Lavish injects a small SDK for annotations, snapshots, feedback controls, and render-time layout checks.
|
|
163
163
|
Author-defined links and popups can open in top-level tabs, while artifact documents remain sandboxed without same-origin access.
|
|
164
164
|
Lavish does not inject any design system, so the saved HTML file renders identically whether you open it through `lavish-axi` or directly in a browser.
|
|
165
|
-
Run `lavish-axi design` for the single source of agent-facing design guidance
|
|
165
|
+
Run `lavish-axi design` for the single source of agent-facing design guidance, including optional CDN snippets and the whiteboard (Mermaid) opt-in snippet.
|
|
166
166
|
- **Self-paint warning** - `lavish-axi <html-file>`, `export`, and `share` run a render-free check for artifacts missing an explicit page background and return a one-line `self_paint_warning`.
|
|
167
167
|
The check fails open - any stylesheet link, `@import`, Tailwind runtime script, `color-scheme`, or `html`/`body`/`:root` background signal suppresses it - and it never blocks the open.
|
|
168
168
|
- **Open-time layout gate** - The browser chrome masks an artifact only while the real in-iframe audit waits for fonts and final geometry.
|
|
@@ -204,6 +204,7 @@ pnpm link
|
|
|
204
204
|
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.
|
|
205
205
|
Codex-specific guidance keeps that poll attached to the active turn instead of hiding it in a background task, because completed background tasks may not resume the agent.
|
|
206
206
|
- **Session end etiquette** - Lavish tracks who ended a session: a human clicking **End session** (or **Send & end session**) in the browser is a user-initiated end, while `lavish-axi end <html-file>` is agent-initiated.
|
|
207
|
+
When either side ends the session, every open review tab becomes visibly read-only and disables its feedback controls; feedback submitted after the end is refused instead of being accepted without an agent to receive it.
|
|
207
208
|
A plain `lavish-axi <html-file>` after a user-initiated end refuses to reopen the browser and returns guidance instead; pass `--reopen` only when the user asks for further review or something important needs their visual attention.
|
|
208
209
|
Agent-initiated ends keep reopening normally, same as before.
|
|
209
210
|
`lavish-axi poll`'s `ended` response and the `feedback` response for the final batch before an end both carry `next_step` guidance telling the agent to stop polling and deliver remaining updates in chat instead of reopening.
|
|
@@ -218,7 +219,8 @@ pnpm link
|
|
|
218
219
|
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.
|
|
219
220
|
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.
|
|
220
221
|
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.
|
|
221
|
-
- **Mermaid diagrams** -
|
|
222
|
+
- **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.
|
|
223
|
+
In the Lavish browser, every rendered Mermaid diagram in a `.mermaid` container becomes an embedded editable Excalidraw whiteboard.
|
|
222
224
|
Click a diagram to unlock editing, and use its Fullscreen action to edit it over the whole viewport.
|
|
223
225
|
Whiteboard scenes autosave locally.
|
|
224
226
|
If a live reload changes the Mermaid source, an unmodified whiteboard silently re-converts to the new diagram. If the reviewer had edited the scene, reopening it lets them re-convert and discard the saved edits or keep editing the saved scene.
|
|
@@ -252,7 +254,7 @@ pnpm link
|
|
|
252
254
|
| `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, and `--password` makes viewers enter the password before viewing. |
|
|
253
255
|
| `lavish-axi stop` | Shut down the background server. |
|
|
254
256
|
| `lavish-axi playbook [id]` | List focused artifact guidance or show one playbook; agents must open each matching playbook before writing HTML. |
|
|
255
|
-
| `lavish-axi design` | Show agent-facing design guidance, including optional CDN and Mermaid
|
|
257
|
+
| `lavish-axi design` | Show agent-facing design guidance, including optional CDN snippets and the whiteboard (Mermaid) opt-in snippet. |
|
|
256
258
|
| `lavish-axi setup hooks` | Install or repair optional SessionStart hooks for Claude Code, Codex, OpenCode, and GitHub Copilot CLI; restart the agent session afterward. |
|
|
257
259
|
| `lavish-axi setup plugin` | Register the installed package as an [Agent Plugin](https://agent-plugins.org) in VS Code, Cursor, and GitHub Copilot CLI; opt-in, idempotent, no marketplace involved. Reload each client afterward. |
|
|
258
260
|
| `lavish-axi server` | Run the local Lavish Editor server. |
|
package/dist/chrome-client.js
CHANGED
|
@@ -746,16 +746,16 @@ function setSheetOpen(open) {
|
|
|
746
746
|
if (sheetOpen) scrollPanelToBottom();
|
|
747
747
|
}
|
|
748
748
|
|
|
749
|
-
// Re-derives every sheet attribute from the
|
|
750
|
-
//
|
|
751
|
-
//
|
|
749
|
+
// Re-derives every sheet attribute from the phone layout, sheet-open, and session-ended state so a
|
|
750
|
+
// viewport crossing the breakpoint in either direction cannot make an ended panel interactive or
|
|
751
|
+
// leave a closed dock trapping focus.
|
|
752
752
|
function applySheetState() {
|
|
753
753
|
const mobile = isMobileSheet();
|
|
754
754
|
const open = mobile && sheetOpen;
|
|
755
755
|
document.body.classList.toggle("sheet-open", open);
|
|
756
756
|
const docked = mobile && !open;
|
|
757
|
-
panelScroll.inert = docked;
|
|
758
|
-
chatComposer.inert = docked;
|
|
757
|
+
panelScroll.inert = ended || docked;
|
|
758
|
+
chatComposer.inert = ended || docked;
|
|
759
759
|
const activeElement = document.activeElement;
|
|
760
760
|
if (docked && activeElement && (panelScroll.contains(activeElement) || chatComposer.contains(activeElement))) {
|
|
761
761
|
panelToggle.focus();
|
|
@@ -1217,6 +1217,14 @@ async function submitQueuedOnce() {
|
|
|
1217
1217
|
if (!response.ok) {
|
|
1218
1218
|
if (response.status === 409) {
|
|
1219
1219
|
const data = await response.json().catch(() => null);
|
|
1220
|
+
// The session already ended before this batch arrived - most likely this chrome missed the
|
|
1221
|
+
// SSE `ended` event (a dropped connection). Go read-only now instead of leaving Send enabled
|
|
1222
|
+
// for another attempt that will be refused the same way.
|
|
1223
|
+
if (data?.status === "ended") {
|
|
1224
|
+
endAfterSubmit = false;
|
|
1225
|
+
markSessionEnded();
|
|
1226
|
+
return false;
|
|
1227
|
+
}
|
|
1220
1228
|
if (Array.isArray(data?.warnings)) setLayoutWarnings(data.warnings);
|
|
1221
1229
|
endAfterSubmit = false;
|
|
1222
1230
|
return false;
|
|
@@ -1778,6 +1786,7 @@ function markSessionEnded() {
|
|
|
1778
1786
|
ended = true;
|
|
1779
1787
|
cancelArtifactLoadRecovery();
|
|
1780
1788
|
closeMenus();
|
|
1789
|
+
closeShareDialog();
|
|
1781
1790
|
closeWarningsDrawer();
|
|
1782
1791
|
renderWarnings();
|
|
1783
1792
|
closeWhiteboard();
|
|
@@ -1785,7 +1794,7 @@ function markSessionEnded() {
|
|
|
1785
1794
|
moreButton.disabled = true;
|
|
1786
1795
|
chatInput.disabled = true;
|
|
1787
1796
|
updateSendState();
|
|
1788
|
-
|
|
1797
|
+
applySheetState();
|
|
1789
1798
|
if (presenceBanner) presenceBanner.hidden = true;
|
|
1790
1799
|
if (handoffBanner) handoffBanner.hidden = true;
|
|
1791
1800
|
if (outdatedBanner) outdatedBanner.hidden = true;
|
|
@@ -3123,6 +3132,7 @@ events.addEventListener("agent-reply", (event) => {
|
|
|
3123
3132
|
events.addEventListener("chat-sync", (event) => syncChat(JSON.parse(event.data).chat || []));
|
|
3124
3133
|
events.addEventListener("agent-presence", (event) => setAgentPresence(JSON.parse(event.data).state));
|
|
3125
3134
|
events.addEventListener("layout-warnings", (event) => setLayoutWarnings(JSON.parse(event.data).warnings || []));
|
|
3135
|
+
events.addEventListener("ended", () => markSessionEnded());
|
|
3126
3136
|
// A reconnecting stream means this chrome may have missed updates while it was away.
|
|
3127
3137
|
events.addEventListener("open", () => refreshLayoutWarnings());
|
|
3128
3138
|
|
|
@@ -3134,6 +3144,9 @@ renderWarnings();
|
|
|
3134
3144
|
initialChat.forEach((item) => addChat(item.role, item.text));
|
|
3135
3145
|
retiredDrafts.forEach((text) => renderRetiredDraft(text));
|
|
3136
3146
|
setAgentPresence("waiting");
|
|
3147
|
+
// The session already ended before this page (re)loaded, so there is no future SSE `ended` event
|
|
3148
|
+
// to wait for - start read-only instead of looking live until a Send gets silently refused.
|
|
3149
|
+
if (sessionData.initialEnded) markSessionEnded();
|
|
3137
3150
|
|
|
3138
3151
|
// Reaching this line is the only proof that this file parsed and ran to completion. The page it
|
|
3139
3152
|
// bootstraps ships with the layout-gate overlay already covering the artifact, and only this
|
package/dist/cli.mjs
CHANGED
|
@@ -20,11 +20,11 @@ var PLAYBOOK_ROUTER_HELP = "One artifact often combines several playbooks (for e
|
|
|
20
20
|
var PLAYBOOKS = [
|
|
21
21
|
{
|
|
22
22
|
id: "diagram",
|
|
23
|
-
use_when: "
|
|
23
|
+
use_when: "Explain relationships, flows, state, architecture, and concepts with illustrations",
|
|
24
24
|
choose: [
|
|
25
|
-
"
|
|
26
|
-
"Use
|
|
27
|
-
"
|
|
25
|
+
"Default to hand-authored inline SVG: it gives proportion, emphasis, spatial metaphor, and annotation-ready structure that generated layouts cannot.",
|
|
26
|
+
"Use Mermaid only when the user asks for an editable whiteboard: rendered Mermaid in a `.mermaid` container becomes an Excalidraw whiteboard in the Lavish browser.",
|
|
27
|
+
"For large systems, draw a small overview illustration and put detail in module cards below it, instead of one dense auto-laid graph."
|
|
28
28
|
],
|
|
29
29
|
structure: [
|
|
30
30
|
"Lead with the question the diagram answers, not with the implementation detail that produced it.",
|
|
@@ -32,15 +32,18 @@ var PLAYBOOKS = [
|
|
|
32
32
|
"For complex systems, separate topology from detail so the overview stays readable."
|
|
33
33
|
],
|
|
34
34
|
design_rules: [
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
35
|
+
"Size with viewBox plus width:100%; never fixed pixel dimensions, and keep every element inside the viewBox.",
|
|
36
|
+
"Color through currentColor and the page's CSS custom properties so figures follow the artifact's light and dark themes.",
|
|
37
|
+
"Give every meaningful node, edge, and region a stable id and a <title> so reviewers can annotate precisely.",
|
|
38
|
+
"Keep labels to a few words and put prose beside the figure in HTML - SVG text does not wrap, so short labels are also the overflow discipline.",
|
|
39
|
+
"Keep figures self-contained: no external images, fonts, or scripts, so exports render offline.",
|
|
40
|
+
"Render-verify before serving: screenshot the artifact in light, dark, and a narrow viewport - the layout audit deliberately skips SVG interiors.",
|
|
41
|
+
"When the user asked for a whiteboard, initialize Mermaid theme-aware with the `lavish-axi design` snippet rather than hardcoding one theme."
|
|
39
42
|
],
|
|
40
43
|
pitfalls: [
|
|
41
|
-
"Do not cram every file or function into one
|
|
42
|
-
"Do not hand-build boxes-and-arrows from div/flexbox
|
|
43
|
-
"Do not
|
|
44
|
+
"Do not cram every file or function into one figure when a layered explanation would be clearer.",
|
|
45
|
+
"Do not hand-build boxes-and-arrows from div/flexbox: inline SVG owns figures, HTML owns the prose around them.",
|
|
46
|
+
"Do not reach for Mermaid to save authoring effort - it surrenders position, size, and emphasis to the engine.",
|
|
44
47
|
"Do not present unverified architecture claims as facts. Cite the files or commands that support them."
|
|
45
48
|
],
|
|
46
49
|
lavish_notes: [
|
|
@@ -402,7 +405,7 @@ var LAYOUT_SAFETY_CSS_SNIPPET = `<style>
|
|
|
402
405
|
}
|
|
403
406
|
</style>`;
|
|
404
407
|
var DESIGN_PRIORITY_RULE = "Decide the design direction in this strict priority order, and only move to the next step when the current one truly yields nothing: (1) if the user asked for a specific look or named design system, use that; (2) otherwise you must first inspect the project the artifact is about - the subject or product whose content or UI it represents, which may differ from your current working directory - and match that project's design system: Tailwind or theme config, shared CSS variables or design tokens, component library, brand assets, or existing styled pages. If the artifact previews, proposes, or mocks a specific app's UI, render it in that app's own design system so it faithfully shows the product, even when you are running in a different repo; (3) only when both steps come up empty, use the Lavish-recommended Tailwind CSS browser runtime v4 + DaisyUI v5, available via CDN, and prefer that CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user.";
|
|
405
|
-
var DESIGN_SYSTEM_HINT = "Lavish does not auto-inject any design system - artifacts stay portable so they render identically when opened directly without lavish-axi running. Before writing any HTML: " + DESIGN_PRIORITY_RULE + " Run `lavish-axi design` for a content-to-playbook router, a copy-pasteable CDN snippet,
|
|
408
|
+
var DESIGN_SYSTEM_HINT = "Lavish does not auto-inject any design system - artifacts stay portable so they render identically when opened directly without lavish-axi running. Before writing any HTML: " + DESIGN_PRIORITY_RULE + " Run `lavish-axi design` for a content-to-playbook router, a copy-pasteable CDN snippet, the whiteboard (Mermaid) opt-in snippet, and the DaisyUI component reference. When you deliver the artifact, state which of the three design sources you used and why.";
|
|
406
409
|
var DAISYUI_THEMES = [
|
|
407
410
|
"light",
|
|
408
411
|
"dark",
|
|
@@ -457,8 +460,8 @@ function createDesignOutput() {
|
|
|
457
460
|
layout_safety_note: "Optional copy-paste CSS for artifacts with dense nested grid/flex layouts, badges, wide monospace or pixel fonts, or local media. Paste it into the artifact yourself when useful. Lavish never auto-injects it, so direct-open portability stays intact.",
|
|
458
461
|
other_design_systems: "If the user asks for a different design system (Bootstrap, custom CSS, plain HTML, etc.), use that instead - Lavish does not require DaisyUI."
|
|
459
462
|
},
|
|
460
|
-
|
|
461
|
-
use_when: "
|
|
463
|
+
whiteboard_tooling: {
|
|
464
|
+
use_when: "Opt-in only: author a diagram as Mermaid in a `.mermaid` container solely when the user asks for an editable whiteboard - Lavish turns it into an Excalidraw whiteboard whose edits come back as feedback. Every other figure is hand-authored inline SVG per the diagram playbook.",
|
|
462
465
|
mermaid_cdn_snippet: MERMAID_CDN_SNIPPET,
|
|
463
466
|
cdn_urls: { mermaid: MERMAID_CDN_URL },
|
|
464
467
|
versions: { mermaid: MERMAID_VERSION }
|
|
@@ -7379,6 +7382,9 @@ var SessionStore = class {
|
|
|
7379
7382
|
const shouldEndSession = Boolean(payload.endSession || payload.end_session);
|
|
7380
7383
|
const restoring = options.restore === true;
|
|
7381
7384
|
const alreadyEnded = session.status === "ended";
|
|
7385
|
+
if (alreadyEnded && !restoring) {
|
|
7386
|
+
return { ended: true, ended_by: session.ended_by };
|
|
7387
|
+
}
|
|
7382
7388
|
const normalized = prompts.map(normalizePrompt);
|
|
7383
7389
|
const normalizedPrompts = normalized.map((entry) => entry.prompt);
|
|
7384
7390
|
const rejected = boundAttachmentRefs(normalized, options);
|
|
@@ -8881,6 +8887,10 @@ async function serve({
|
|
|
8881
8887
|
res.status(404).json({ error: "session not found" });
|
|
8882
8888
|
return;
|
|
8883
8889
|
}
|
|
8890
|
+
if (result.ended) {
|
|
8891
|
+
res.status(409).json({ status: "ended", error: "session already ended", ended_by: result.ended_by });
|
|
8892
|
+
return;
|
|
8893
|
+
}
|
|
8884
8894
|
if (result.rejected) {
|
|
8885
8895
|
res.status(400).json({
|
|
8886
8896
|
error: "some attachments could not be delivered",
|
|
@@ -8904,7 +8914,7 @@ async function serve({
|
|
|
8904
8914
|
await syncOutstandingRepairs(req.params.key);
|
|
8905
8915
|
events.emit("layout-warnings", req.params.key, serializeLayoutWarnings(session.layout_warnings));
|
|
8906
8916
|
}
|
|
8907
|
-
events.emit(shouldEndSession ? "ended" : "feedback", req.params.key);
|
|
8917
|
+
events.emit(shouldEndSession ? "ended" : "feedback", req.params.key, session.ended_by);
|
|
8908
8918
|
res.json({ status: "queued", pending_prompts: session.pending_prompts });
|
|
8909
8919
|
if (shouldEndSession) await shutdownIfNoLiveSessions();
|
|
8910
8920
|
} catch (error) {
|
|
@@ -8991,9 +9001,9 @@ async function serve({
|
|
|
8991
9001
|
});
|
|
8992
9002
|
app.post("/api/:key/end", async (req, res, next) => {
|
|
8993
9003
|
try {
|
|
8994
|
-
await store.endSession(req.params.key, "user");
|
|
9004
|
+
const session = await store.endSession(req.params.key, "user");
|
|
8995
9005
|
clearFeedbackDelivery(req.params.key, activePolls, deliveredFeedback, events);
|
|
8996
|
-
events.emit("ended", req.params.key);
|
|
9006
|
+
events.emit("ended", req.params.key, session?.ended_by);
|
|
8997
9007
|
res.json({ status: "ended" });
|
|
8998
9008
|
await shutdownIfNoLiveSessions();
|
|
8999
9009
|
} catch (error) {
|
|
@@ -9080,9 +9090,9 @@ async function serve({
|
|
|
9080
9090
|
try {
|
|
9081
9091
|
const file = await canonicalFile(req.body.file);
|
|
9082
9092
|
const key = sessionKey(file);
|
|
9083
|
-
await store.endSession(key, "agent");
|
|
9093
|
+
const session = await store.endSession(key, "agent");
|
|
9084
9094
|
clearFeedbackDelivery(key, activePolls, deliveredFeedback, events);
|
|
9085
|
-
events.emit("ended", key);
|
|
9095
|
+
events.emit("ended", key, session?.ended_by);
|
|
9086
9096
|
res.json({ status: "ended" });
|
|
9087
9097
|
await shutdownIfNoLiveSessions();
|
|
9088
9098
|
} catch (error) {
|
|
@@ -9215,6 +9225,8 @@ async function serve({
|
|
|
9215
9225
|
}
|
|
9216
9226
|
});
|
|
9217
9227
|
app.get("/events/:key", async (req, res, next) => {
|
|
9228
|
+
let cleanup = () => {
|
|
9229
|
+
};
|
|
9218
9230
|
try {
|
|
9219
9231
|
res.writeHead(200, {
|
|
9220
9232
|
"content-type": "text/event-stream",
|
|
@@ -9223,7 +9235,6 @@ async function serve({
|
|
|
9223
9235
|
});
|
|
9224
9236
|
sseClients.set(res, String(req.params.key || ""));
|
|
9225
9237
|
refreshIdleTimer();
|
|
9226
|
-
const session = await store.findByKey(req.params.key);
|
|
9227
9238
|
const sendReload = (key) => {
|
|
9228
9239
|
if (key === req.params.key) {
|
|
9229
9240
|
res.write("event: reload\ndata: {}\n\n");
|
|
@@ -9253,29 +9264,51 @@ data: ${JSON.stringify({ warnings })}
|
|
|
9253
9264
|
`);
|
|
9254
9265
|
}
|
|
9255
9266
|
};
|
|
9256
|
-
|
|
9257
|
-
|
|
9267
|
+
const sendEnded = (key, endedBy) => {
|
|
9268
|
+
if (key === req.params.key) {
|
|
9269
|
+
res.write(`event: ended
|
|
9270
|
+
data: ${JSON.stringify({ ended_by: endedBy || null })}
|
|
9258
9271
|
|
|
9259
9272
|
`);
|
|
9260
|
-
|
|
9261
|
-
|
|
9262
|
-
data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, deliveredFeedback) })}
|
|
9263
|
-
|
|
9264
|
-
`
|
|
9265
|
-
);
|
|
9273
|
+
}
|
|
9274
|
+
};
|
|
9266
9275
|
events.on("reload", sendReload);
|
|
9267
9276
|
events.on("agent-reply", sendAgentReply);
|
|
9268
9277
|
events.on("agent-presence", sendPresence);
|
|
9269
9278
|
events.on("layout-warnings", sendLayoutWarnings);
|
|
9270
|
-
|
|
9279
|
+
events.on("ended", sendEnded);
|
|
9280
|
+
let cleanedUp = false;
|
|
9281
|
+
cleanup = () => {
|
|
9282
|
+
if (cleanedUp) return;
|
|
9283
|
+
cleanedUp = true;
|
|
9284
|
+
req.off("close", cleanup);
|
|
9271
9285
|
sseClients.delete(res);
|
|
9272
9286
|
events.off("reload", sendReload);
|
|
9273
9287
|
events.off("agent-reply", sendAgentReply);
|
|
9274
9288
|
events.off("agent-presence", sendPresence);
|
|
9275
9289
|
events.off("layout-warnings", sendLayoutWarnings);
|
|
9290
|
+
events.off("ended", sendEnded);
|
|
9276
9291
|
refreshIdleTimer();
|
|
9277
|
-
}
|
|
9292
|
+
};
|
|
9293
|
+
req.once("close", cleanup);
|
|
9294
|
+
const session = await store.findByKey(req.params.key);
|
|
9295
|
+
if (req.destroyed || res.writableEnded) {
|
|
9296
|
+
cleanup();
|
|
9297
|
+
return;
|
|
9298
|
+
}
|
|
9299
|
+
res.write(`event: chat-sync
|
|
9300
|
+
data: ${JSON.stringify({ chat: session?.chat || [] })}
|
|
9301
|
+
|
|
9302
|
+
`);
|
|
9303
|
+
res.write(
|
|
9304
|
+
`event: agent-presence
|
|
9305
|
+
data: ${JSON.stringify({ state: computePresence(req.params.key, activePolls, deliveredFeedback) })}
|
|
9306
|
+
|
|
9307
|
+
`
|
|
9308
|
+
);
|
|
9309
|
+
if (session?.status === "ended") sendEnded(req.params.key, session.ended_by);
|
|
9278
9310
|
} catch (error) {
|
|
9311
|
+
cleanup();
|
|
9279
9312
|
next(error);
|
|
9280
9313
|
}
|
|
9281
9314
|
});
|
|
@@ -10035,6 +10068,11 @@ function createChromeHtml(session, {
|
|
|
10035
10068
|
const sessionJson = jsonScript({
|
|
10036
10069
|
key: session.key,
|
|
10037
10070
|
file: session.file,
|
|
10071
|
+
// A page loaded (or reloaded) after the session already ended has no future SSE `ended`
|
|
10072
|
+
// event to wait for - it must start read-only instead of looking live until the user tries
|
|
10073
|
+
// to send and gets refused (#171).
|
|
10074
|
+
initialEnded: session.status === "ended",
|
|
10075
|
+
initialEndedBy: session.ended_by || null,
|
|
10038
10076
|
initialChat: session.chat || [],
|
|
10039
10077
|
// Bootstrapping the inbox from the server is what makes it survive a browser refresh or a
|
|
10040
10078
|
// reconnect: the chrome never owns warning state, it only renders it.
|
|
@@ -10334,7 +10372,7 @@ var POLL_WAKE_PATH_RULES = Object.freeze([
|
|
|
10334
10372
|
]);
|
|
10335
10373
|
var POLL_SEND_AND_END_RULE = "`Send & End` ends the session. Its final feedback is still delivered once. After that response, polling stops, and the agent must not reopen the session uninvited.";
|
|
10336
10374
|
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.";
|
|
10337
|
-
var VERSION = "0.1.
|
|
10375
|
+
var VERSION = "0.1.58";
|
|
10338
10376
|
function detectInvokingAgent(env = process.env) {
|
|
10339
10377
|
return ["CODEX_SANDBOX", "CODEX_THREAD_ID"].some((key) => Object.hasOwn(env, key)) ? "codex" : "generic";
|
|
10340
10378
|
}
|
|
@@ -10440,10 +10478,10 @@ function createHomeOutput({ bin, sessions, includeSessions = true, agent = "gene
|
|
|
10440
10478
|
} : {},
|
|
10441
10479
|
visual_guidance: [
|
|
10442
10480
|
"Use visual hierarchy to make the most important decisions, risks, tradeoffs, and next actions obvious at a glance",
|
|
10443
|
-
"
|
|
10481
|
+
"Show, don't tell: explain concepts, flows, relationships, and comparisons with labeled illustrations - hand-authored inline SVG, per the diagram playbook - and show existing UI or state with screenshots of the real pages (run the app read-only if needed); reserve prose for what cannot be shown, such as rationale, trade-offs, and open questions",
|
|
10482
|
+
"Structure the prose that remains with sections, cards, tables, annotated snippets, and side-by-side comparisons instead of long paragraphs",
|
|
10444
10483
|
"Choose typography, spacing, color, and layout deliberately so the artifact has a clear point of view",
|
|
10445
|
-
"Prevent horizontal overflow at every nesting level: nested grid/flex children also need minmax(0, 1fr) tracks and min-width: 0, especially when badges, labels, or status text use wide pixel or monospace fonts; wrap, truncate, or contain long unbreakable text deliberately"
|
|
10446
|
-
"When the artifact would describe existing or current UI or state, show it instead: capture screenshots of the real pages (run the app read-only if needed) and embed them, rather than explaining the current look in prose; reserve prose for what cannot be shown such as rationale, trade-offs, and open questions"
|
|
10484
|
+
"Prevent horizontal overflow at every nesting level: nested grid/flex children also need minmax(0, 1fr) tracks and min-width: 0, especially when badges, labels, or status text use wide pixel or monospace fonts; wrap, truncate, or contain long unbreakable text deliberately"
|
|
10447
10485
|
],
|
|
10448
10486
|
playbooks: listPlaybooks(),
|
|
10449
10487
|
help: [
|
|
@@ -10451,7 +10489,7 @@ function createHomeOutput({ bin, sessions, includeSessions = true, agent = "gene
|
|
|
10451
10489
|
"Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`",
|
|
10452
10490
|
"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",
|
|
10453
10491
|
`Run \`lavish-axi poll <html-file>\` to wait for user feedback. It long-polls and stays silent until the user sends feedback or ends the session, 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}`,
|
|
10454
|
-
'
|
|
10492
|
+
'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)',
|
|
10455
10493
|
"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`",
|
|
10456
10494
|
"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",
|
|
10457
10495
|
"Run `lavish-axi share <html-file> [--password <pw>] [--token <t>]` to publish the artifact on ht-ml.app (https://ht-ml.app), a third-party hosting service not part of Lavish, and get back a visitable URL. Shares are PUBLIC by default, so anyone with the link can open them. Pass --password to publish a PRIVATE password-protected page; viewers must supply the password to view. Local assets are inlined; remote refs load over the network. It returns the url plus a secret update_key for managing the page later. Use --token or LAVISH_AXI_HTML_APP_TOKEN only when you have an optional bearer token; it is never required. Users can also publish from the browser chrome's overflow menu",
|
|
@@ -11459,7 +11497,7 @@ Examples:
|
|
|
11459
11497
|
`,
|
|
11460
11498
|
design: `Usage: lavish-axi design
|
|
11461
11499
|
|
|
11462
|
-
Show a copy-pasteable CDN snippet for Tailwind CSS browser runtime v4 + DaisyUI v5 + themes, Mermaid
|
|
11500
|
+
Show a copy-pasteable CDN snippet for Tailwind CSS browser runtime v4 + DaisyUI v5 + themes, the whiteboard (Mermaid) opt-in snippet, a content-to-playbook router, an optional layout safety CSS snippet, plus technical reference for DaisyUI components. ${PLAYBOOK_ROUTER_HELP} Lavish artifacts stay portable HTML. This CDN snippet is the design fallback, not the default: inspect the subject project before falling back, and paste the layout safety CSS only when useful for dense nested grid/flex layouts, badges, wide fonts, or local media. ${DESIGN_PRIORITY_RULE}
|
|
11463
11501
|
`,
|
|
11464
11502
|
setup: `Usage: lavish-axi setup hooks
|
|
11465
11503
|
lavish-axi setup plugin
|
package/package.json
CHANGED
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.
|
|
4
|
+
"version": "0.1.58",
|
|
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",
|
package/skills/lavish/SKILL.md
CHANGED
|
@@ -52,18 +52,18 @@ Use lavish-axi when the user asks for a visual artifact, HTML explainer, interac
|
|
|
52
52
|
## Visual guidance
|
|
53
53
|
|
|
54
54
|
- Use visual hierarchy to make the most important decisions, risks, tradeoffs, and next actions obvious at a glance
|
|
55
|
-
-
|
|
55
|
+
- Show, don't tell: explain concepts, flows, relationships, and comparisons with labeled illustrations - hand-authored inline SVG, per the diagram playbook - and show existing UI or state with screenshots of the real pages (run the app read-only if needed); reserve prose for what cannot be shown, such as rationale, trade-offs, and open questions
|
|
56
|
+
- Structure the prose that remains with sections, cards, tables, annotated snippets, and side-by-side comparisons instead of long paragraphs
|
|
56
57
|
- Choose typography, spacing, color, and layout deliberately so the artifact has a clear point of view
|
|
57
58
|
- Prevent horizontal overflow at every nesting level: nested grid/flex children also need minmax(0, 1fr) tracks and min-width: 0, especially when badges, labels, or status text use wide pixel or monospace fonts; wrap, truncate, or contain long unbreakable text deliberately
|
|
58
|
-
- When the artifact would describe existing or current UI or state, show it instead: capture screenshots of the real pages (run the app read-only if needed) and embed them, rather than explaining the current look in prose; reserve prose for what cannot be shown such as rationale, trade-offs, and open questions
|
|
59
59
|
|
|
60
60
|
## Playbooks
|
|
61
61
|
|
|
62
62
|
Run `npx -y lavish-axi playbook <id>` for focused, detailed guidance on any of these.
|
|
63
63
|
One artifact often combines several playbooks (for example a plan that includes a comparison and a diagram), so MUST open each matching playbook before writing HTML.
|
|
64
|
-
|
|
64
|
+
Figures are hand-authored inline SVG by default - open the diagram playbook before drawing, and never build boxes-and-arrows from div/flexbox. Use the Mermaid whiteboard snippet from `npx -y lavish-axi design` only when the user asks for an editable whiteboard.
|
|
65
65
|
|
|
66
|
-
- `diagram` -
|
|
66
|
+
- `diagram` - Explain relationships, flows, state, architecture, and concepts with illustrations
|
|
67
67
|
- `table` - Turn dense records into scan-friendly review surfaces
|
|
68
68
|
- `comparison` - Show options, tradeoffs, and current vs target behavior
|
|
69
69
|
- `plan` - Explain a product or technical plan before implementation
|
|
@@ -77,11 +77,11 @@ For flows, architecture, state, or sequence diagrams, do not hand-build boxes-an
|
|
|
77
77
|
- Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`
|
|
78
78
|
- 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
|
|
79
79
|
- Run `npx -y lavish-axi poll <html-file>` to wait for user feedback. It long-polls and stays silent until the user sends feedback or ends the session, 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. Keep the poll in the foreground by default and let it return the feedback directly to the agent. A background poll is allowed only through a harness-native tracked background-job facility whose completion result is guaranteed to resume or notify the same agent. Never use `nohup`, shell `&`, `disown`, redirected fire-and-forget processes, or a detached terminal without an explicit verified callback merely to keep polling alive. If the harness has no completion-aware background facility, use the foreground poll or first wire a verified wake callback into the surrounding supervisor. Do not tell the user the artifact is being monitored until that wake path is live. If the poll gets killed or times out before feedback arrives, re-run it - feedback remains queued until delivery. Poll delivery consumes the response, so read it completely. `Send & End` ends the session. Its final feedback is still delivered once. After that response, polling stops, and the agent must not reopen the session uninvited.
|
|
80
|
-
-
|
|
80
|
+
- 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)
|
|
81
81
|
- Run `npx -y 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 `npx -y lavish-axi <html-file>` refuses to reopen it without `--reopen`
|
|
82
82
|
- Run `npx -y 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
|
|
83
83
|
- Run `npx -y lavish-axi share <html-file> [--password <pw>] [--token <t>]` to publish the artifact on ht-ml.app (https://ht-ml.app), a third-party hosting service not part of Lavish, and get back a visitable URL. Shares are PUBLIC by default, so anyone with the link can open them. Pass --password to publish a PRIVATE password-protected page; viewers must supply the password to view. Local assets are inlined; remote refs load over the network. It returns the url plus a secret update_key for managing the page later. Use --token or LAVISH_AXI_HTML_APP_TOKEN only when you have an optional bearer token; it is never required. Users can also publish from the browser chrome's overflow menu
|
|
84
84
|
- Run `npx -y lavish-axi stop` to shut down the background server (it also self-stops when idle or after the last session ends with nothing connected)
|
|
85
85
|
- Run `npx -y lavish-axi playbook <playbook_id>` for focused artifact guidance. One artifact often combines several playbooks (for example a plan that includes a comparison and a diagram), so MUST open each matching playbook before writing HTML.
|
|
86
|
-
- Lavish does not auto-inject any design system - artifacts stay portable so they render identically when opened directly without lavish-axi running. Before writing any HTML: Decide the design direction in this strict priority order, and only move to the next step when the current one truly yields nothing: (1) if the user asked for a specific look or named design system, use that; (2) otherwise you must first inspect the project the artifact is about - the subject or product whose content or UI it represents, which may differ from your current working directory - and match that project's design system: Tailwind or theme config, shared CSS variables or design tokens, component library, brand assets, or existing styled pages. If the artifact previews, proposes, or mocks a specific app's UI, render it in that app's own design system so it faithfully shows the product, even when you are running in a different repo; (3) only when both steps come up empty, use the Lavish-recommended Tailwind CSS browser runtime v4 + DaisyUI v5, available via CDN, and prefer that CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user. Run `npx -y lavish-axi design` for a content-to-playbook router, a copy-pasteable CDN snippet,
|
|
86
|
+
- Lavish does not auto-inject any design system - artifacts stay portable so they render identically when opened directly without lavish-axi running. Before writing any HTML: Decide the design direction in this strict priority order, and only move to the next step when the current one truly yields nothing: (1) if the user asked for a specific look or named design system, use that; (2) otherwise you must first inspect the project the artifact is about - the subject or product whose content or UI it represents, which may differ from your current working directory - and match that project's design system: Tailwind or theme config, shared CSS variables or design tokens, component library, brand assets, or existing styled pages. If the artifact previews, proposes, or mocks a specific app's UI, render it in that app's own design system so it faithfully shows the product, even when you are running in a different repo; (3) only when both steps come up empty, use the Lavish-recommended Tailwind CSS browser runtime v4 + DaisyUI v5, available via CDN, and prefer that CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user. Run `npx -y lavish-axi design` for a content-to-playbook router, a copy-pasteable CDN snippet, the whiteboard (Mermaid) opt-in snippet, and the DaisyUI component reference. When you deliver the artifact, state which of the three design sources you used and why.
|
|
87
87
|
- Use lavish-axi when the user asks for a visual artifact, HTML explainer, interactive prototype, review surface, product or technical plan, comparison, report, or browser-based feedback loop
|