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.
Files changed (3) hide show
  1. package/README.md +10 -7
  2. package/dist/cli.mjs +76 -40
  3. 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
- - **Sandboxed artifact** - The artifact runs in an iframe while Lavish injects a small SDK for annotations, snapshots, and feedback controls, plus Tailwind CSS v4 and DaisyUI v5 design assets unless the HTML includes `<meta name="lavish-design" content="off">`.
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 the injected Tailwind CSS and DaisyUI design reference. |
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 DESIGN_SYSTEM_HINT = 'Lavish auto-injects Tailwind CSS browser runtime v4, DaisyUI v5, and DaisyUI themes into artifacts. Do not add these libraries separately. Set <html data-theme="luxury"> or another DaisyUI theme as appropriate, then use DaisyUI/Tailwind classes directly. Prefer using DaisyUI/Tailwind for styling to avoid having to spend tokens on writing a custom stylesheet, but in cases where you need precise control on the artifact, you can opt out of the auto-injected styles by adding <meta name="lavish-design" content="off">. Run `lavish-axi design` for technical reference.';
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 artifacts already have Tailwind CSS browser runtime v4, DaisyUI v5, and DaisyUI themes available.",
55
- rule: "Do not add Tailwind or DaisyUI script/link tags yourself.",
56
- opt_out: 'Add `<meta name="lavish-design" content="off">` to disable injection for exact-fidelity artifacts.',
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
- const design = shouldInjectDesign(html) ? '<link rel="stylesheet" href="/design/daisyui.css" data-lavish-design><script src="/design/tailwindcss-browser.js" data-lavish-design></script><link rel="stylesheet" href="/design/daisyui-themes.css" data-lavish-design>' : "";
996
- const withDesign = injectDesignAssets(html, design);
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
- ${design}`;
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
- watchSession(session, watchers, events);
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 root = path3.dirname(session.file);
1506
- const watcher = chokidar.watch(root, {
1507
- ignored: /(^|[/\\])(\.git|node_modules|dist|build|\.lavish-axi)([/\\]|$)/,
1508
- ignoreInitial: true,
1509
- awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 }
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.9";
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: `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.`
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}, then 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.`
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 server = await serve({ port, stateFile: stateFile(), version: VERSION });
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 technical reference for the Tailwind CSS browser runtime v4, DaisyUI v5 components, and DaisyUI themes that Lavish auto-injects into artifacts. Do not add these libraries separately.
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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lavish-axi",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
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",