lavish-axi 0.1.9 → 0.1.11
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 +10 -7
- package/dist/cli.mjs +76 -40
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,7 +95,8 @@ pnpm link
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
- **File-path identity** - Sessions are keyed by the canonical HTML file path, so agents do not need opaque IDs.
|
|
98
|
-
- **
|
|
98
|
+
- **Portable artifacts** - The artifact runs in an iframe while Lavish injects a small SDK for annotations, snapshots, and feedback controls. 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. Run `lavish-axi design` for a copy-pasteable CDN snippet to opt in to Tailwind CSS v4 + DaisyUI v5, or use any other design system (or none).
|
|
99
|
+
- **Live reload** - Lavish watches the HTML artifact file by default. To also reload on sibling asset changes, add `data-lavish-live-reload-root` to the root element or `<meta name="lavish-live-reload" content="root">`.
|
|
99
100
|
- **Feedback controls** - Mark buttons, choices, and other interactive elements with `data-lavish-action` so Lavish does not annotate them, then call `window.lavish.queuePrompt()` or `window.lavish.sendQueuedPrompts()` from the control handler.
|
|
100
101
|
- **Agent presence** - The browser shows when no agent is listening, still queues feedback for the next `lavish-axi poll`, and only blocks sending while the agent is working on delivered feedback.
|
|
101
102
|
- **Precise targets** - Text annotations include selected text plus range anchors, so agents are not limited to whole-element selectors.
|
|
@@ -110,17 +111,19 @@ pnpm link
|
|
|
110
111
|
| `lavish-axi poll <html-file>` | Long-poll until the user sends feedback or ends the session. |
|
|
111
112
|
| `lavish-axi end <html-file>` | End a session. |
|
|
112
113
|
| `lavish-axi playbook [id]` | List focused artifact guidance or show one playbook. |
|
|
113
|
-
| `lavish-axi design` | Show
|
|
114
|
+
| `lavish-axi design` | Show CDN snippet + DaisyUI component reference (opt-in). |
|
|
115
|
+
| `lavish-axi server` | Run the local Lavish Editor server. |
|
|
114
116
|
|
|
115
117
|
Known playbook IDs: `diagram`, `table`, `comparison`, `plan`, `diff`, `input`, `slides`.
|
|
116
118
|
|
|
117
119
|
### Flags
|
|
118
120
|
|
|
119
|
-
| Command | Flag | Description
|
|
120
|
-
| ------------------------ | --------------------- |
|
|
121
|
-
| `lavish-axi <html-file>` | `--no-open` | Ensure the server/session exists without opening another browser window.
|
|
122
|
-
| `lavish-axi poll` | `--agent-reply "..."` | Show the agent's reply in the existing browser chat before polling again.
|
|
123
|
-
| `lavish-axi poll` | `--timeout-ms <ms>` | Test/debug escape hatch only; agents should normally omit it.
|
|
121
|
+
| Command | Flag | Description |
|
|
122
|
+
| ------------------------ | --------------------- | ---------------------------------------------------------------------------------------- |
|
|
123
|
+
| `lavish-axi <html-file>` | `--no-open` | Ensure the server/session exists without opening another browser window. |
|
|
124
|
+
| `lavish-axi poll` | `--agent-reply "..."` | Show the agent's reply in the existing browser chat before polling again. |
|
|
125
|
+
| `lavish-axi poll` | `--timeout-ms <ms>` | Test/debug escape hatch only; agents should normally omit it. |
|
|
126
|
+
| `lavish-axi server` | `--verbose` | Log session and watcher events to stderr; can also be enabled with `LAVISH_AXI_DEBUG=1`. |
|
|
124
127
|
|
|
125
128
|
## Development
|
|
126
129
|
|
package/dist/cli.mjs
CHANGED
|
@@ -10,7 +10,17 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
import { AxiError, runAxiCli } from "axi-sdk-js";
|
|
11
11
|
|
|
12
12
|
// src/design-reference.js
|
|
13
|
-
var
|
|
13
|
+
var TAILWIND_BROWSER_VERSION = "4.2.4";
|
|
14
|
+
var DAISYUI_VERSION = "5.5.19";
|
|
15
|
+
var DESIGN_CDN_URLS = {
|
|
16
|
+
tailwind: `https://cdn.jsdelivr.net/npm/@tailwindcss/browser@${TAILWIND_BROWSER_VERSION}/dist/index.global.js`,
|
|
17
|
+
daisyui: `https://cdn.jsdelivr.net/npm/daisyui@${DAISYUI_VERSION}/daisyui.css`,
|
|
18
|
+
daisyuiThemes: `https://cdn.jsdelivr.net/npm/daisyui@${DAISYUI_VERSION}/themes.css`
|
|
19
|
+
};
|
|
20
|
+
var DESIGN_CDN_SNIPPET = `<link rel="stylesheet" href="${DESIGN_CDN_URLS.daisyui}">
|
|
21
|
+
<link rel="stylesheet" href="${DESIGN_CDN_URLS.daisyuiThemes}">
|
|
22
|
+
<script src="${DESIGN_CDN_URLS.tailwind}"></script>`;
|
|
23
|
+
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. Tailwind CSS browser runtime v4 and DaisyUI v5 are available via CDN; run `lavish-axi design` for a copy-pasteable CDN snippet plus component reference. Prefer that CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user. If the user asks for any other design system or plain HTML, follow that request.";
|
|
14
24
|
var DAISYUI_THEMES = [
|
|
15
25
|
"light",
|
|
16
26
|
"dark",
|
|
@@ -51,11 +61,13 @@ var DAISYUI_THEMES = [
|
|
|
51
61
|
function createDesignOutput() {
|
|
52
62
|
return {
|
|
53
63
|
design: {
|
|
54
|
-
summary: "Lavish
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
summary: "Lavish does not auto-inject any design system. Artifacts stay portable HTML. Paste the CDN snippet below into your `<head>` to use Tailwind CSS browser runtime v4 + DaisyUI v5 + themes. Prefer this CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user.",
|
|
65
|
+
cdn_snippet: DESIGN_CDN_SNIPPET,
|
|
66
|
+
cdn_urls: DESIGN_CDN_URLS,
|
|
67
|
+
versions: { tailwind: TAILWIND_BROWSER_VERSION, daisyui: DAISYUI_VERSION },
|
|
57
68
|
latest_docs: "https://daisyui.com/components/",
|
|
58
|
-
docs_note: "Use this command for common syntax. Read the latest DaisyUI docs for full details when using advanced or unfamiliar components."
|
|
69
|
+
docs_note: "Use this command for common syntax. Read the latest DaisyUI docs for full details when using advanced or unfamiliar components.",
|
|
70
|
+
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."
|
|
59
71
|
},
|
|
60
72
|
theme_usage: [
|
|
61
73
|
'Set the page theme with `<html data-theme="luxury">`.',
|
|
@@ -992,25 +1004,11 @@ function createArtifactSdk() {
|
|
|
992
1004
|
// src/html-transform.js
|
|
993
1005
|
function injectLavishSdk(html, key) {
|
|
994
1006
|
const script = `<script src="/sdk.js?key=${encodeURIComponent(key)}"></script>`;
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
if (/<\/body\s*>/i.test(withDesign)) {
|
|
998
|
-
return withDesign.replace(/<\/body\s*>/i, `${script}</body>`);
|
|
999
|
-
}
|
|
1000
|
-
return `${withDesign}
|
|
1001
|
-
${script}`;
|
|
1002
|
-
}
|
|
1003
|
-
function shouldInjectDesign(html) {
|
|
1004
|
-
if (/data-lavish-design/i.test(html)) return false;
|
|
1005
|
-
return !/<meta\b(?=[^>]*name=["']lavish-design["'])(?=[^>]*content=["']off["'])[^>]*>/i.test(html);
|
|
1006
|
-
}
|
|
1007
|
-
function injectDesignAssets(html, design) {
|
|
1008
|
-
if (!design) return html;
|
|
1009
|
-
if (/<head\b[^>]*>/i.test(html)) {
|
|
1010
|
-
return html.replace(/<head\b[^>]*>/i, (match) => `${match}${design}`);
|
|
1007
|
+
if (/<\/body\s*>/i.test(html)) {
|
|
1008
|
+
return html.replace(/<\/body\s*>/i, `${script}</body>`);
|
|
1011
1009
|
}
|
|
1012
1010
|
return `${html}
|
|
1013
|
-
${
|
|
1011
|
+
${script}`;
|
|
1014
1012
|
}
|
|
1015
1013
|
|
|
1016
1014
|
// src/session-store.js
|
|
@@ -1181,7 +1179,7 @@ var designAssetUrls = {
|
|
|
1181
1179
|
type: "application/javascript"
|
|
1182
1180
|
}
|
|
1183
1181
|
};
|
|
1184
|
-
async function serve({ port, stateFile: stateFile2, version = "" }) {
|
|
1182
|
+
async function serve({ port, stateFile: stateFile2, version = "", debug = false, log = null }) {
|
|
1185
1183
|
const app = express();
|
|
1186
1184
|
const store = new SessionStore(stateFile2);
|
|
1187
1185
|
const events = new EventEmitter();
|
|
@@ -1189,6 +1187,10 @@ async function serve({ port, stateFile: stateFile2, version = "" }) {
|
|
|
1189
1187
|
const activePolls = /* @__PURE__ */ new Map();
|
|
1190
1188
|
const deliveredFeedback = /* @__PURE__ */ new Set();
|
|
1191
1189
|
const sseClients = /* @__PURE__ */ new Set();
|
|
1190
|
+
const verbose = debug || process.env.LAVISH_AXI_DEBUG === "1";
|
|
1191
|
+
const writeLog = typeof log === "function" ? log : (line) => process.stderr.write(`${line}
|
|
1192
|
+
`);
|
|
1193
|
+
const logEvent = verbose ? (line) => writeLog(`[lavish] ${line}`) : null;
|
|
1192
1194
|
app.use(express.json({ limit: "2mb" }));
|
|
1193
1195
|
app.get("/health", (req, res) => {
|
|
1194
1196
|
res.json({ ok: true, app: "lavish-axi", version });
|
|
@@ -1211,7 +1213,8 @@ async function serve({ port, stateFile: stateFile2, version = "" }) {
|
|
|
1211
1213
|
if (existing?.status === "ended") {
|
|
1212
1214
|
clearFeedbackDelivery(key, activePolls, deliveredFeedback, events);
|
|
1213
1215
|
}
|
|
1214
|
-
|
|
1216
|
+
logEvent?.(`session opened key=${key} file=${file}`);
|
|
1217
|
+
await watchSession(session, watchers, events, logEvent);
|
|
1215
1218
|
res.json({ key, file, url, status: "opened" });
|
|
1216
1219
|
} catch (error) {
|
|
1217
1220
|
next(error);
|
|
@@ -1320,7 +1323,7 @@ async function serve({ port, stateFile: stateFile2, version = "" }) {
|
|
|
1320
1323
|
res.status(404).send("Session not found");
|
|
1321
1324
|
return;
|
|
1322
1325
|
}
|
|
1323
|
-
watchSession(session, watchers, events);
|
|
1326
|
+
await watchSession(session, watchers, events, logEvent);
|
|
1324
1327
|
res.type("html").send(createChromeHtml(session));
|
|
1325
1328
|
} catch (error) {
|
|
1326
1329
|
next(error);
|
|
@@ -1498,23 +1501,55 @@ function resolveArtifactAsset(root, assetPath) {
|
|
|
1498
1501
|
}
|
|
1499
1502
|
return file;
|
|
1500
1503
|
}
|
|
1501
|
-
function watchSession(session, watchers, events) {
|
|
1504
|
+
async function watchSession(session, watchers, events, logEvent) {
|
|
1502
1505
|
if (watchers.has(session.key)) {
|
|
1503
1506
|
return;
|
|
1504
1507
|
}
|
|
1505
|
-
const
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1508
|
+
const target = await resolveWatchTarget(session);
|
|
1509
|
+
if (watchers.has(session.key)) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
logEvent?.(`watch session=${session.key} scope=${target.scope} path=${target.path}`);
|
|
1513
|
+
const watcher = chokidar.watch(target.path, target.options);
|
|
1511
1514
|
let timer = null;
|
|
1512
|
-
watcher.on("all", () => {
|
|
1515
|
+
watcher.on("all", (event, file) => {
|
|
1516
|
+
logEvent?.(`watch event=${event} session=${session.key} file=${file ?? ""}`);
|
|
1513
1517
|
clearTimeout(timer);
|
|
1514
1518
|
timer = setTimeout(() => events.emit("reload", session.key), 100);
|
|
1515
1519
|
});
|
|
1520
|
+
watcher.on("error", (error) => {
|
|
1521
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1522
|
+
logEvent?.(`watch error session=${session.key} message=${message}`);
|
|
1523
|
+
});
|
|
1516
1524
|
watchers.set(session.key, watcher);
|
|
1517
1525
|
}
|
|
1526
|
+
async function resolveWatchTarget(session) {
|
|
1527
|
+
const baseOptions = {
|
|
1528
|
+
ignoreInitial: true,
|
|
1529
|
+
awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
|
|
1530
|
+
};
|
|
1531
|
+
try {
|
|
1532
|
+
const html = await readFile2(session.file, "utf8");
|
|
1533
|
+
if (hasLiveReloadRootOptIn(html)) {
|
|
1534
|
+
return {
|
|
1535
|
+
path: path3.dirname(session.file),
|
|
1536
|
+
scope: "directory",
|
|
1537
|
+
options: {
|
|
1538
|
+
...baseOptions,
|
|
1539
|
+
ignored: /(^|[/\\])(\.git|node_modules|dist|build|\.lavish-axi)([/\\]|$)/
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
}
|
|
1543
|
+
} catch {
|
|
1544
|
+
}
|
|
1545
|
+
return { path: session.file, scope: "file", options: baseOptions };
|
|
1546
|
+
}
|
|
1547
|
+
function hasLiveReloadRootOptIn(html) {
|
|
1548
|
+
if (typeof html !== "string") return false;
|
|
1549
|
+
const searchableHtml = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
1550
|
+
if (/<html\b[^>]*\sdata-lavish-live-reload-root(?:[\s=>/]|$)[^>]*>/i.test(searchableHtml)) return true;
|
|
1551
|
+
return /<meta\b(?=[^>]*name=["']lavish-live-reload["'])(?=[^>]*content=["']root["'])[^>]*>/i.test(searchableHtml);
|
|
1552
|
+
}
|
|
1518
1553
|
function setPollActive(key, activePolls, deliveredFeedback, events, active) {
|
|
1519
1554
|
const previousPresence = computePresence(key, activePolls, deliveredFeedback);
|
|
1520
1555
|
const count = activePolls.get(key) || 0;
|
|
@@ -1758,7 +1793,7 @@ function normalizePagePath(path5) {
|
|
|
1758
1793
|
// src/cli.js
|
|
1759
1794
|
var COMMANDS = /* @__PURE__ */ new Set(["open", "poll", "end", "server", "playbook", "design"]);
|
|
1760
1795
|
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`.";
|
|
1761
|
-
var VERSION = "0.1.
|
|
1796
|
+
var VERSION = "0.1.11";
|
|
1762
1797
|
async function run(argv) {
|
|
1763
1798
|
await ensureStateDir();
|
|
1764
1799
|
const normalizedArgv = normalizeArgv(argv);
|
|
@@ -1875,7 +1910,7 @@ function createPlaybookOutput(args) {
|
|
|
1875
1910
|
function createOpenOutput({ file, url, status }) {
|
|
1876
1911
|
return {
|
|
1877
1912
|
session: { file, url, status },
|
|
1878
|
-
next_step: `
|
|
1913
|
+
next_step: `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 or ends the session. Do not pass --timeout-ms during normal agent use. Do not set a short shell timeout; either run it without a timeout or set the shell timeout above 10 minutes. After applying feedback, run \`lavish-axi poll ${file} --agent-reply "<message for the user>"\` without --timeout-ms to show your response in Lavish Editor and wait for more feedback.`
|
|
1879
1914
|
};
|
|
1880
1915
|
}
|
|
1881
1916
|
async function openCommand(args) {
|
|
@@ -1927,7 +1962,7 @@ function createPollOutput({ file, response }) {
|
|
|
1927
1962
|
session: { file, status: "feedback" },
|
|
1928
1963
|
dom_snapshot: response.dom_snapshot || "",
|
|
1929
1964
|
prompts: response.prompts || [],
|
|
1930
|
-
next_step: `Apply the requested changes to ${file}
|
|
1965
|
+
next_step: `Apply the requested changes to ${file}. 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. The poll command waits until the user sends more feedback or ends the session; do not set a short shell timeout, or set the shell timeout above 10 minutes.`
|
|
1931
1966
|
};
|
|
1932
1967
|
}
|
|
1933
1968
|
if (response.status === "ended") {
|
|
@@ -1956,7 +1991,8 @@ async function designCommand() {
|
|
|
1956
1991
|
}
|
|
1957
1992
|
async function serverCommand(args) {
|
|
1958
1993
|
const port = Number(flagValue(args, "--port") || defaultPort());
|
|
1959
|
-
const
|
|
1994
|
+
const debug = args.includes("--verbose") || process.env.LAVISH_AXI_DEBUG === "1";
|
|
1995
|
+
const server = await serve({ port, stateFile: stateFile(), version: VERSION, debug });
|
|
1960
1996
|
await server.done;
|
|
1961
1997
|
return "";
|
|
1962
1998
|
}
|
|
@@ -2152,11 +2188,11 @@ Examples:
|
|
|
2152
2188
|
`,
|
|
2153
2189
|
design: `Usage: lavish-axi design
|
|
2154
2190
|
|
|
2155
|
-
Show
|
|
2191
|
+
Show a copy-pasteable CDN snippet for Tailwind CSS browser runtime v4 + DaisyUI v5 + themes, plus technical reference for DaisyUI components. Lavish artifacts stay portable HTML. Prefer the CDN snippet over hand-writing styles unless explicitly instructed otherwise by the user. If the user asks for another design system or plain HTML, follow that request.
|
|
2156
2192
|
`,
|
|
2157
|
-
server: `Usage: lavish-axi server [--port 4387]
|
|
2193
|
+
server: `Usage: lavish-axi server [--port 4387] [--verbose]
|
|
2158
2194
|
|
|
2159
|
-
Run the local Lavish Editor server.
|
|
2195
|
+
Run the local Lavish Editor server. Pass --verbose (or set LAVISH_AXI_DEBUG=1) to log session and watcher events to stderr.
|
|
2160
2196
|
`
|
|
2161
2197
|
};
|
|
2162
2198
|
|