lavish-axi 0.1.0 → 0.1.3

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
@@ -22,6 +22,10 @@
22
22
 
23
23
  <h3 align="center">For when a rich editor is not rich enough.</h3>
24
24
 
25
+ <p align="center">
26
+ <img alt="Lavish Editor demo" src="lavish-editor-marketing/renders/lavish-editor-marketing.gif" width="960" />
27
+ </p>
28
+
25
29
  HTML is the new markdown. Lavish is the new editor for your HTML artifacts.
26
30
 
27
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.”
@@ -107,6 +111,7 @@ npm link
107
111
  ## Development
108
112
 
109
113
  ```sh
114
+ npm run check # Run all verification commands
110
115
  npm run build # Bundle the publishable CLI
111
116
  npm test # Run node:test tests
112
117
  npm run lint # Run ESLint
package/dist/cli.mjs CHANGED
@@ -4,7 +4,6 @@
4
4
  import { spawn } from "node:child_process";
5
5
  import { access } from "node:fs/promises";
6
6
  import os2 from "node:os";
7
- import path4 from "node:path";
8
7
  import { fileURLToPath } from "node:url";
9
8
  import { AxiError, runAxiCli } from "axi-sdk-js";
10
9
 
@@ -439,6 +438,7 @@ function setPollActive(key, activePolls, events, active) {
439
438
  }
440
439
  function createChromeHtml(session) {
441
440
  const initialChat = JSON.stringify(session.chat || []);
441
+ const fileInputSize = Math.max(1, session.file.length);
442
442
  return `<!doctype html>
443
443
  <html>
444
444
  <head>
@@ -446,19 +446,14 @@ function createChromeHtml(session) {
446
446
  <meta name="viewport" content="width=device-width, initial-scale=1">
447
447
  <title>Lavish Editor</title>
448
448
  <style>
449
- body{margin:0;background:#0f1115;color:#f7f3ea;font:14px/1.4 ui-sans-serif,system-ui,sans-serif;overflow:hidden}
450
- .bar{height:56px;display:flex;align-items:center;gap:12px;padding:0 16px;background:#171a21;border-bottom:1px solid #2a2f3a;box-sizing:border-box}
451
- .brand{font-weight:750;letter-spacing:.02em}.file{color:#b9c0cf;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
452
- button{border:0;border-radius:10px;padding:9px 12px;background:#f4c95d;color:#17130a;font-weight:700;cursor:pointer}button:disabled{opacity:.55;cursor:not-allowed}
453
- button.secondary{background:#2a2f3a;color:#f7f3ea}button.danger{background:#f06464;color:white}
454
- .layout{height:calc(100vh - 56px);min-height:0;display:grid;grid-template-columns:1fr 360px}.frame{background:white}.panel{border-left:1px solid #2a2f3a;background:#11141a;display:flex;flex-direction:column;min-width:0;min-height:0}
455
- .panel h2{font-size:15px;margin:16px 16px 8px}.chat{flex:1;min-height:0;overflow:auto;padding:0 16px 12px;display:flex;flex-direction:column;gap:10px}.bubble{border-radius:14px;padding:10px 12px;background:#1c212b;border:1px solid #303745}.bubble.user{background:#25230f;border-color:#5d4d1b}.bubble.agent{background:#172419;border-color:#315f3a}.bubble.agent-working{display:flex;align-items:center;gap:8px;color:#d8deea}.spinner{width:14px;height:14px;border-radius:999px;border:2px solid #315f3a;border-top-color:#8fe39e;animation:spin .8s linear infinite}.bubble small{display:block;color:#aeb6c6;margin-bottom:4px}@keyframes spin{to{transform:rotate(360deg)}}.composer{display:grid;gap:8px;padding:12px 16px;border-top:1px solid #2a2f3a;min-width:0;flex-shrink:0;box-sizing:border-box}.annotation-pills{display:flex;flex-wrap:wrap;gap:6px;min-width:0}.pill-wrap{position:relative;max-width:100%}.pill{display:flex;align-items:center;gap:6px;max-width:100%;border:1px solid #3c4557;border-radius:999px;background:#1c212b;color:#d8deea;padding:5px 7px 5px 9px;font-size:12px;font-weight:750}.pill-preview{display:block;max-width:220px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.pill-close{width:18px;height:18px;border:0;border-radius:999px;padding:0;background:#303745;color:#d8deea;line-height:18px;font-size:14px}.pill-tooltip{display:none;position:absolute;z-index:5;left:0;bottom:calc(100% + 8px);width:min(320px,80vw);border:1px solid #3c4557;border-radius:12px;background:#171a21;color:#d8deea;padding:10px;font-size:12px;font-weight:500;box-shadow:0 16px 44px rgba(0,0,0,.35)}.tooltip-label{color:#8c96aa;font-size:10px;font-weight:900;letter-spacing:.08em;text-transform:uppercase;margin:0 0 4px}.pill-tooltip-target{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;background:#0f1115;border:1px solid #303745;border-radius:8px;padding:6px;margin-bottom:8px;overflow-wrap:anywhere}.pill-tooltip-prompt{white-space:pre-wrap;overflow-wrap:anywhere}.pill-wrap:hover .pill-tooltip{display:block}.composer textarea{width:100%;max-width:100%;min-width:0;min-height:82px;resize:vertical;border-radius:12px;border:1px solid #303745;background:#0f1115;color:#f7f3ea;padding:10px;font:inherit;box-sizing:border-box}.actions{display:flex;gap:8px}
456
- iframe{width:100%;height:100%;border:0;background:white}
449
+ :root{--ink-900:#0f1115;--ink-800:#11141a;--ink-700:#171a21;--ink-600:#1c212b;--steel-700:#2a2f3a;--steel-600:#303745;--steel-500:#3c4557;--steel-400:#8c96aa;--steel-300:#aeb6c6;--steel-200:#b9c0cf;--steel-100:#d8deea;--cream-50:#fffbf3;--cream-100:#f7f3ea;--cream-200:#e8e1cf;--brass-500:#f4c95d;--brass-400:#ffd877;--brass-ink:#17130a;--sage-900:#172419;--sage-700:#315f3a;--sage-300:#8fe39e;--amber-900:#25230f;--amber-700:#5d4d1b;--rust-500:#f06464;--bg:var(--ink-900);--bg-panel:var(--ink-800);--bg-bar:var(--ink-700);--bg-elevated:var(--ink-600);--fg:var(--cream-100);--fg-muted:var(--steel-100);--fg-dim:var(--steel-200);--fg-faint:var(--steel-300);--fg-label:var(--steel-400);--border:var(--steel-600);--border-subtle:var(--steel-700);--border-strong:var(--steel-500);--accent:var(--brass-500);--accent-hover:var(--brass-400);--accent-ink:var(--brass-ink);--danger:var(--rust-500);--font-serif:"EB Garamond","Iowan Old Style",Georgia,serif;--font-sans:Geist,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;--font-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--text-xs:12px;--lh-xs:1.35;--text-sm:13px;--lh-sm:1.4;--text-base:14px;--lh-base:1.45;--text-md:16px;--lh-md:1.5;--text-lg:18px;--lh-lg:1.45;--text-xl:22px;--lh-xl:1.3;--text-2xl:28px;--lh-2xl:1.25;--text-3xl:36px;--lh-3xl:1.18;--text-4xl:48px;--lh-4xl:1.1;--text-5xl:64px;--lh-5xl:1.05;--text-display:92px;--lh-display:1;--w-regular:400;--w-medium:500;--w-semi:600;--w-bold:700;--w-brand:750;--track-tight:-.01em;--track-normal:0;--track-brand:.02em;--track-label:.08em;--track-caps:.12em;--space-1:2px;--space-2:4px;--space-3:6px;--space-4:8px;--space-5:10px;--space-6:12px;--space-8:16px;--space-10:20px;--space-12:24px;--space-16:32px;--space-20:40px;--space-24:48px;--space-32:64px;--radius-sm:8px;--radius-md:10px;--radius-lg:12px;--radius-xl:14px;--radius-pill:999px;--hairline:1px solid var(--border);--hairline-subtle:1px solid var(--border-subtle);--shadow-tooltip:0 16px 44px rgba(0,0,0,.35);--shadow-floating:0 20px 70px rgba(0,0,0,.35);--bar-h:56px;--panel-w:360px;--ease:cubic-bezier(.2,.6,.2,1);--dur-fast:120ms;--dur:180ms;--dur-slow:320ms;--annotate-outline:2px solid var(--accent);--annotate-offset:2px}
450
+ *{box-sizing:border-box}html,body{margin:0;width:100%;height:100%}body{background:var(--bg);color:var(--fg);font-family:var(--font-sans);font-size:var(--text-base);line-height:var(--lh-base);overflow:hidden;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}body.lavish{color-scheme:dark}:focus-visible{outline:var(--annotate-outline);outline-offset:var(--annotate-offset)}.bar{height:var(--bar-h);display:flex;align-items:center;gap:14px;padding:0 var(--space-8);background:var(--bg-bar);border-bottom:var(--hairline-subtle);box-sizing:border-box}.brand{display:flex;align-items:flex-end;height:22px;gap:8px;white-space:nowrap;flex-shrink:0}.brand-mark{font-family:var(--font-serif);font-style:italic;font-size:22px;line-height:1;color:var(--fg)}.brand-support{font-family:var(--font-sans);font-size:10px;font-weight:600;letter-spacing:.18em;text-transform:uppercase;color:var(--fg-muted);position:relative;top:1px}.divider{width:1px;height:22px;background:var(--border);flex:0 0 auto}.file-wrap{display:flex;align-items:center;height:22px;gap:8px;flex:1 1 auto;min-width:0;color:var(--fg-muted);font-family:var(--font-mono);font-size:12px}.file-input{display:block;min-width:0;width:auto;max-width:100%;border:1px solid var(--border-subtle);border-radius:var(--radius-sm);background:transparent;color:var(--fg-muted);font:inherit;font-family:var(--font-mono);font-size:12px;line-height:1;padding:4px 7px;text-overflow:ellipsis}.copy-button{border:1px solid var(--border);border-radius:var(--radius-sm);background:transparent;color:var(--fg-faint);font:inherit;font-family:var(--font-sans);font-size:11px;font-weight:600;line-height:1;padding:4px 7px;white-space:nowrap;cursor:pointer}.copy-button:hover{color:var(--fg);border-color:var(--border-strong)}.button{border:0;border-radius:var(--radius-md);padding:9px 14px;background:var(--accent);color:var(--accent-ink);font-family:inherit;font-size:var(--text-sm);font-weight:var(--w-bold);white-space:nowrap;cursor:pointer;transition:background 120ms ease-out,color 120ms ease-out,opacity 120ms ease-out,border-color 120ms ease-out}.button:hover:not(:disabled){background:var(--accent-hover)}.button:active:not(:disabled){opacity:.85}.button:disabled{opacity:.55;cursor:not-allowed}.button.secondary{background:var(--border-subtle);color:var(--fg);border:1px solid transparent;font-weight:var(--w-semi)}.button.secondary:hover:not(:disabled){background:var(--border)}.button.annotation-on{border:1px solid var(--accent)}.button.danger{background:transparent;color:var(--danger);border:1px solid var(--danger);font-weight:var(--w-semi)}.button.danger:hover:not(:disabled){background:rgba(240,100,100,.1)}.layout{height:calc(100vh - var(--bar-h));min-height:0;display:grid;grid-template-columns:minmax(0,1fr) var(--panel-w)}.frame{min-width:0;min-height:0;background:#fff}.panel{width:var(--panel-w);border-left:var(--hairline-subtle);background:var(--bg-panel);display:flex;flex-direction:column;min-width:0;min-height:0}.panel h2{font-size:15px;line-height:1.3;margin:16px 16px 8px;font-weight:var(--w-semi);letter-spacing:0}.chat{flex:1;min-height:0;overflow:auto;padding:0 16px 12px;display:flex;flex-direction:column;gap:10px}.bubble{max-width:85%;border-radius:var(--radius-xl);padding:10px 12px;background:var(--bg-elevated);border:var(--hairline);color:var(--fg)}.bubble.user{align-self:flex-end;background:var(--bg-elevated);border-color:var(--border-strong)}.bubble.agent{align-self:flex-start;background:transparent;border-color:var(--border-subtle)}.bubble.agent-working{display:flex;align-items:center;gap:8px;color:var(--fg-muted)}.spinner{width:14px;height:14px;border-radius:var(--radius-pill);border:2px solid var(--border);border-top-color:var(--accent);animation:spin .8s linear infinite;flex:0 0 auto}.bubble small{display:block;color:var(--fg-faint);margin-bottom:4px;font-size:10px;font-weight:var(--w-bold);letter-spacing:var(--track-label);text-transform:uppercase}.bubble.user small{text-align:right}@keyframes spin{to{transform:rotate(360deg)}}.composer{display:grid;gap:8px;padding:12px 16px;border-top:var(--hairline-subtle);min-width:0;flex-shrink:0;box-sizing:border-box}.annotation-pills{display:flex;flex-wrap:wrap;gap:6px;min-width:0}.pill-wrap{position:relative;max-width:100%}.pill{display:flex;align-items:center;gap:6px;max-width:100%;border:1px solid var(--border-strong);border-radius:var(--radius-pill);background:var(--bg-elevated);color:var(--fg-muted);padding:5px 7px 5px 11px;font-size:12px;font-weight:var(--w-bold)}.pill-preview{display:block;max-width:220px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.pill-close{width:18px;height:18px;border:0;border-radius:var(--radius-pill);padding:0;background:var(--border);color:var(--fg-muted);line-height:18px;font-size:14px;cursor:pointer}.pill-tooltip{display:none;position:absolute;z-index:5;left:0;bottom:calc(100% + 8px);width:min(320px,80vw);border:1px solid var(--border-strong);border-radius:var(--radius-lg);background:var(--bg-bar);color:var(--fg-muted);padding:10px;font-size:12px;font-weight:500;box-shadow:var(--shadow-tooltip)}.tooltip-label{color:var(--fg-label);font-size:10px;font-weight:900;letter-spacing:var(--track-label);text-transform:uppercase;margin:0 0 4px}.pill-tooltip-target{font-family:var(--font-mono);background:var(--bg);border:1px solid var(--border);border-radius:var(--radius-sm);padding:6px;margin-bottom:8px;overflow-wrap:anywhere}.pill-tooltip-prompt{white-space:pre-wrap;overflow-wrap:anywhere}.pill-wrap:hover .pill-tooltip,.pill-wrap:focus-within .pill-tooltip{display:block}.composer textarea{width:100%;max-width:100%;min-width:0;min-height:82px;resize:vertical;border-radius:var(--radius-lg);border:1px solid var(--border);background:var(--bg);color:var(--fg);padding:10px;font:inherit;font-family:var(--font-sans);box-sizing:border-box}.composer textarea::placeholder{color:var(--fg-label)}.actions{display:flex;gap:8px;justify-content:flex-end}.ended-view{height:calc(100vh - var(--bar-h));display:grid;place-items:center;padding:24px;background:var(--bg)}.ended-card{width:min(360px,100%);border:1px solid var(--border);border-radius:var(--radius-xl);background:var(--bg-panel);padding:20px 24px;text-align:center}.ended-title{font-family:var(--font-serif);font-style:italic;font-size:26px;line-height:1.2;color:var(--fg);margin-bottom:8px}.ended-copy{margin:0;color:var(--fg-faint);font-family:var(--font-mono);font-size:12px;line-height:1.45}iframe{width:100%;height:100%;border:0;background:white}
451
+ @media (max-width:860px){body{overflow:auto}.bar{min-height:var(--bar-h);height:auto;align-items:flex-start;flex-wrap:wrap;padding:10px 12px}.divider{display:none}.file-wrap{order:3;flex-basis:100%;font-size:11px}.layout{height:calc(100vh - var(--bar-h));grid-template-columns:1fr;grid-template-rows:minmax(0,1fr) min(42vh,360px)}.panel{width:100%;border-left:0;border-top:var(--hairline-subtle)}.chat{padding-bottom:10px}.pill-preview{max-width:min(220px,70vw)}}
457
452
  </style>
458
453
  </head>
459
- <body>
460
- <div class="bar"><div class="brand">Lavish Editor</div><div class="file">${escapeHtml(session.file)}</div><button id="annotation">Annotation: On</button><button class="danger" id="end">End Session</button></div>
461
- <div class="layout"><div class="frame"><iframe id="artifact" sandbox="allow-scripts allow-forms allow-popups allow-downloads" src="/artifact/${session.key}/index.html"></iframe></div><aside class="panel"><h2>Conversation</h2><div class="chat" id="chatLog"></div><div class="composer"><div class="annotation-pills" id="annotationPills"></div><textarea id="chatInput" placeholder="Write a message for the agent..."></textarea><div class="actions"><button id="send">Send to Agent</button></div></div></aside></div>
454
+ <body class="lavish">
455
+ <div class="bar"><div class="brand"><span class="brand-mark">Lavish</span><span class="brand-support">Editor</span></div><div class="divider" aria-hidden="true"></div><div class="file-wrap" title="${escapeHtml(session.file)}"><input class="file-input" id="filePath" readonly size="${fileInputSize}" value="${escapeHtml(session.file)}"><button class="copy-button" id="copyPath" type="button">Copy Path</button></div><button class="button secondary annotation-on" id="annotation">Annotation: On</button><button class="button danger" id="end">End Session</button></div>
456
+ <div class="layout"><div class="frame"><iframe id="artifact" sandbox="allow-scripts allow-forms allow-popups allow-downloads" src="/artifact/${session.key}/index.html"></iframe></div><aside class="panel"><h2>Conversation</h2><div class="chat" id="chatLog"></div><div class="composer"><div class="annotation-pills" id="annotationPills"></div><textarea id="chatInput" placeholder="Write a message for the agent..."></textarea><div class="actions"><button class="button" id="send">Send to Agent</button></div></div></aside></div>
462
457
  <script>
463
458
  const key=${JSON.stringify(session.key)};
464
459
  const initialChat=${initialChat};
@@ -467,6 +462,8 @@ const annotationPills=document.getElementById('annotationPills');
467
462
  const chatLog=document.getElementById('chatLog');
468
463
  const chatInput=document.getElementById('chatInput');
469
464
  const sendButton=document.getElementById('send');
465
+ const filePathInput=document.getElementById('filePath');
466
+ const copyPathButton=document.getElementById('copyPath');
470
467
  const queued=[];
471
468
  let annotation=true;
472
469
  let agentPolling=false;
@@ -480,12 +477,13 @@ function setAgentPolling(active){agentPolling=!!active;sendButton.disabled=!agen
480
477
  function removeQueuedPrompt(index,event){if(event)event.stopPropagation();queued.splice(index,1);render()}
481
478
  function postToFrame(message){frame.contentWindow&&frame.contentWindow.postMessage(message,'*')}
482
479
  window.addEventListener('message',event=>{if(event.source!==frame.contentWindow)return;const msg=event.data||{};if(msg.type==='lavish:queuePrompt'){queued.push(msg.prompt);render()}if(msg.type==='lavish:snapshot'){pendingSnapshot=msg.snapshot||'';submitQueued()}if(msg.type==='lavish:sendQueuedPrompts'){sendQueued()}if(msg.type==='lavish:endSession'){endSession()}});
483
- document.getElementById('annotation').onclick=()=>{annotation=!annotation;document.getElementById('annotation').textContent='Annotation: '+(annotation?'On':'Off');postToFrame({type:'lavish:setAnnotationMode',enabled:annotation})};
484
- sendButton.onclick=sendQueued;document.getElementById('end').onclick=endSession;
480
+ document.getElementById('annotation').onclick=()=>{annotation=!annotation;document.getElementById('annotation').textContent='Annotation: '+(annotation?'On':'Off');document.getElementById('annotation').classList.toggle('annotation-on',annotation);postToFrame({type:'lavish:setAnnotationMode',enabled:annotation})};
481
+ async function copyFilePath(){try{await navigator.clipboard.writeText(filePathInput.value)}catch{filePathInput.select();document.execCommand('copy')}copyPathButton.textContent='Copied';setTimeout(()=>{copyPathButton.textContent='Copy Path'},1200)}
482
+ sendButton.onclick=sendQueued;copyPathButton.onclick=copyFilePath;document.getElementById('end').onclick=endSession;
485
483
  frame.addEventListener('load',()=>postToFrame({type:'lavish:setAnnotationMode',enabled:annotation}));
486
484
  function sendQueued(){if(!agentPolling)return;const text=chatInput.value.trim();if(text){queued.push({uid:'',prompt:text,selector:'',tag:'message',text:'Freeform message'});addChat('user',text);chatInput.value='';render()}if(!queued.length)return;postToFrame({type:'lavish:requestSnapshot'})}
487
485
  async function submitQueued(){const prompts=queued.splice(0,queued.length);render();setAgentPolling(false);await fetch('/api/'+key+'/prompts',{method:'POST',headers:{'content-type':'application/json'},body:JSON.stringify({prompts,domSnapshot:pendingSnapshot})})}
488
- async function endSession(){await fetch('/api/'+key+'/end',{method:'POST'});document.body.innerHTML='<div class="bar"><div class="brand">Lavish Editor</div><div class="file">Session ended. The agent polling loop can stop.</div></div>'}
486
+ async function endSession(){await fetch('/api/'+key+'/end',{method:'POST'});document.body.innerHTML='<div class="bar"><div class="brand"><span class="brand-mark">Lavish</span><span class="brand-support">Editor</span></div></div><main class="ended-view"><section class="ended-card"><div class="ended-title">Session ended.</div><p class="ended-copy">Return to your agent to continue.</p></section></main>'}
489
487
  const events=new EventSource('/events/'+key);events.addEventListener('reload',()=>{frame.src=frame.src});events.addEventListener('agent-reply',event=>addChat('agent',JSON.parse(event.data).text));events.addEventListener('chat-sync',event=>syncChat(JSON.parse(event.data).chat||[]));events.addEventListener('agent-working',event=>setAgentPolling(!JSON.parse(event.data).working));render();initialChat.forEach(item=>addChat(item.role,item.text));setAgentPolling(false);
490
488
  </script>
491
489
  </body>
@@ -505,16 +503,16 @@ function uid(el){if(!ids.has(el))ids.set(el,String(++counter));return ids.get(el
505
503
  function selector(el){if(!el||!el.tagName)return'';const parts=[];let node=el;while(node&&node.nodeType===1&&parts.length<5){let part=node.tagName.toLowerCase();if(node.id){part+='#'+CSS.escape(node.id);parts.unshift(part);break}const parent=node.parentElement;if(parent){const same=[...parent.children].filter(x=>x.tagName===node.tagName);if(same.length>1)part+=':nth-of-type('+(same.indexOf(node)+1)+')'}parts.unshift(part);node=parent}return parts.join(' > ')}
506
504
  function context(el){return{uid:uid(el),selector:selector(el),tag:(el.tagName||'').toLowerCase(),text:(el.innerText||el.textContent||'').trim().replace(/\\s+/g,' ').slice(0,240)}}
507
505
  function isLavishUi(el){return !!(el&&el.closest&&el.closest('[data-lavish-ui]'))}
508
- function highlightElement(el){if(el){el.style.outline='2px solid #f4c95d';el.style.outlineOffset='2px'}}
506
+ function highlightElement(el){if(el){el.style.outline='var(--lavish-annotate-outline,2px solid #f4c95d)';el.style.outlineOffset='var(--lavish-annotate-offset,2px)'}}
509
507
  function clearHighlight(el){if(el)el.style.outline=''}
510
- function setAnnotationMode(enabled){annotationMode=!!enabled;let style=document.getElementById('lavish-cursor-style');if(annotationMode&&!style){style=document.createElement('style');style.id='lavish-cursor-style';style.textContent='*{cursor:default!important}';document.head.appendChild(style)}if(!annotationMode&&style)style.remove();if(!annotationMode)closeCard()}
508
+ function setAnnotationMode(enabled){annotationMode=!!enabled;let style=document.getElementById('lavish-cursor-style');if(annotationMode&&!style){style=document.createElement('style');style.id='lavish-cursor-style';style.textContent=':root{--lavish-accent:#f4c95d;--lavish-annotate-outline:2px solid var(--lavish-accent);--lavish-annotate-offset:2px}*{cursor:default!important}';document.head.appendChild(style)}if(!annotationMode&&style)style.remove();if(!annotationMode)closeCard()}
511
509
  function queuePrompt(prompt,options={}){const item={...context(options.element||document.activeElement||document.body),prompt:String(prompt||'')};if(options.uid)item.uid=String(options.uid);if(options.selector)item.selector=String(options.selector);if(options.tag)item.tag=String(options.tag);if(options.text)item.text=String(options.text);if(options.data)item.prompt+='\\n\\nContext data:\\n'+JSON.stringify(options.data,null,2);parent.postMessage({type:'lavish:queuePrompt',prompt:item},'*')}
512
510
  function sendQueuedPrompts(){parent.postMessage({type:'lavish:sendQueuedPrompts'},'*')}
513
511
  function endSession(){parent.postMessage({type:'lavish:endSession'},'*')}
514
512
  function snapshot(){const lines=[];function walk(el,depth){if(!(el instanceof Element)||depth>6||isLavishUi(el))return;const c=context(el);const name=c.text?' "'+c.text.slice(0,80).replace(/"/g,"'")+'"':'';lines.push(' '.repeat(depth)+'uid='+c.uid+' '+c.tag+name);for(const child of el.children)walk(child,depth+1)}walk(document.body,0);return lines.join('\\n')}
515
- function ensureShadow(){if(shadow)return shadow;host=document.createElement('div');host.className='lavish-annotation-root';host.setAttribute('data-lavish-ui','annotation-root');document.documentElement.appendChild(host);shadow=host.attachShadow({mode:'open'});const style=document.createElement('style');style.textContent=':host{all:initial;position:fixed;z-index:2147483647;left:0;top:0;color-scheme:dark;font-family:ui-sans-serif,system-ui,sans-serif}*{box-sizing:border-box}.lavish-annotation-card{position:fixed;width:min(320px,calc(100vw - 24px));padding:12px;border-radius:14px;background:#11141a;color:#f7f3ea;border:1px solid #f4c95d;box-shadow:0 20px 70px rgba(0,0,0,.35);font:14px/1.4 ui-sans-serif,system-ui,sans-serif}.lavish-annotation-card textarea{width:100%;min-height:86px;resize:vertical;border-radius:10px;border:1px solid #303745;background:#0f1115;color:#f7f3ea;padding:9px;font:inherit}.lavish-annotation-card .lavish-row{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}.lavish-annotation-card button{border:0;border-radius:9px;padding:8px 10px;font-weight:800;cursor:pointer}.lavish-annotation-card .lavish-send{background:#f4c95d;color:#17130a}.lavish-annotation-card .lavish-cancel{background:#2a2f3a;color:#f7f3ea}';shadow.appendChild(style);return shadow}
513
+ function ensureShadow(){if(shadow)return shadow;host=document.createElement('div');host.className='lavish-annotation-root';host.setAttribute('data-lavish-ui','annotation-root');document.documentElement.appendChild(host);shadow=host.attachShadow({mode:'open'});const style=document.createElement('style');style.textContent=':host{all:initial;position:fixed;z-index:2147483647;left:0;top:0;color-scheme:dark;--ink-900:#0f1115;--ink-800:#11141a;--ink-700:#171a21;--ink-600:#1c212b;--steel-700:#2a2f3a;--steel-600:#303745;--steel-500:#3c4557;--steel-400:#8c96aa;--steel-300:#aeb6c6;--steel-200:#b9c0cf;--steel-100:#d8deea;--cream-50:#fffbf3;--cream-100:#f7f3ea;--cream-200:#e8e1cf;--brass-500:#f4c95d;--brass-400:#ffd877;--brass-ink:#17130a;--bg:var(--ink-900);--bg-panel:var(--ink-800);--bg-elevated:var(--ink-600);--fg:var(--cream-100);--fg-faint:var(--steel-300);--border:var(--steel-600);--accent:#f4c95d;--accent-hover:#ffd877;--font-sans:Geist,ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;--font-mono:"Geist Mono",ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;--radius-md:10px;--radius-xl:14px;--shadow-floating:0 20px 70px rgba(0,0,0,.35);font-family:var(--font-sans)}*{box-sizing:border-box}:focus-visible{outline:2px solid var(--accent);outline-offset:2px}.lavish-annotation-card{position:fixed;width:min(320px,calc(100vw - 24px));padding:12px;border-radius:var(--radius-xl);background:var(--bg-panel);color:var(--fg);border:1px solid var(--accent);box-shadow:var(--shadow-floating);font:14px/1.4 var(--font-sans)}.lavish-heading{font-weight:700;margin-bottom:6px}.lavish-annotation-card textarea{width:100%;min-height:86px;resize:vertical;border-radius:var(--radius-md);border:1px solid var(--border);background:var(--bg);color:var(--fg);padding:9px;font:inherit;font-family:var(--font-sans)}.lavish-annotation-card textarea::placeholder{color:var(--fg-faint)}.lavish-annotation-card .lavish-row{display:flex;gap:8px;justify-content:flex-end;margin-top:8px}.lavish-annotation-card button{border:0;border-radius:var(--radius-md);padding:8px 10px;font-family:var(--font-sans);font-size:13px;font-weight:700;cursor:pointer}.lavish-annotation-card button:active{opacity:.85}.lavish-annotation-card .lavish-send{background:var(--accent);color:var(--brass-ink)}.lavish-annotation-card .lavish-send:hover{background:var(--accent-hover)}.lavish-annotation-card .lavish-cancel{background:var(--steel-700);color:var(--fg)}';shadow.appendChild(style);return shadow}
516
514
  function closeCard(){if(shadow){for(const el of [...shadow.querySelectorAll('.lavish-annotation-card')])el.remove()}clearHighlight(selected);selected=null}
517
- function showAnnotationCard(target){const root=ensureShadow();closeCard();selected=target;highlightElement(selected);const c=context(target);const rect=target.getBoundingClientRect();const card=document.createElement('div');card.className='lavish-annotation-card';card.innerHTML='<div style="font-weight:800;margin-bottom:6px">Annotate &lt;'+c.tag+'&gt;</div><textarea placeholder="Tell the agent what to change about this element..."></textarea><div class="lavish-row"><button class="lavish-cancel" type="button">Cancel</button><button class="lavish-send" type="button">Queue Prompt</button></div>';root.appendChild(card);const left=Math.min(Math.max(12,rect.left),window.innerWidth-card.offsetWidth-12);const top=Math.min(Math.max(12,rect.bottom+8),window.innerHeight-card.offsetHeight-12);card.style.left=left+'px';card.style.top=top+'px';const textarea=card.querySelector('textarea');card.querySelector('.lavish-cancel').onclick=closeCard;card.querySelector('.lavish-send').onclick=()=>{const prompt=textarea.value.trim();if(prompt)queuePrompt(prompt,c);closeCard()};setTimeout(()=>textarea.focus(),0)}
515
+ function showAnnotationCard(target){const root=ensureShadow();closeCard();selected=target;highlightElement(selected);const c=context(target);const rect=target.getBoundingClientRect();const card=document.createElement('div');card.className='lavish-annotation-card';card.innerHTML='<div class="lavish-heading">Annotate &lt;'+c.tag+'&gt;</div><textarea placeholder="Tell the agent what to change about this element..."></textarea><div class="lavish-row"><button class="lavish-cancel" type="button">Cancel</button><button class="lavish-send" type="button">Queue</button></div>';root.appendChild(card);const left=Math.min(Math.max(12,rect.left),window.innerWidth-card.offsetWidth-12);const top=Math.min(Math.max(12,rect.bottom+8),window.innerHeight-card.offsetHeight-12);card.style.left=left+'px';card.style.top=top+'px';const textarea=card.querySelector('textarea');card.querySelector('.lavish-cancel').onclick=closeCard;card.querySelector('.lavish-send').onclick=()=>{const prompt=textarea.value.trim();if(prompt)queuePrompt(prompt,c);closeCard()};setTimeout(()=>textarea.focus(),0)}
518
516
  window.lavish={queuePrompt,sendQueuedPrompts,endSession,getQueuedPrompts:()=>[],setStatus:message=>parent.postMessage({type:'lavish:status',message:String(message)},'*'),snapshot};
519
517
  window.addEventListener('message',event=>{const msg=event.data||{};if(msg.type==='lavish:setAnnotationMode')setAnnotationMode(msg.enabled);if(msg.type==='lavish:requestSnapshot')parent.postMessage({type:'lavish:snapshot',snapshot:snapshot()},'*')});
520
518
  document.addEventListener('mouseover',event=>{if(!annotationMode||isLavishUi(event.target))return;if(event.target===selected)return;if(hovered&&hovered!==selected)clearHighlight(hovered);hovered=event.target;highlightElement(hovered)},true);
@@ -616,9 +614,9 @@ var HttpTelemetryClient = class {
616
614
  if (!trimmed) return;
617
615
  this.send(trimmed, eventURL(this.app, trimmed), fields);
618
616
  }
619
- pageview(path5, fields = {}) {
617
+ pageview(path4, fields = {}) {
620
618
  if (this.closed) return;
621
- this.send("", normalizePagePath(path5), fields);
619
+ this.send("", normalizePagePath(path4), fields);
622
620
  }
623
621
  async close(timeoutMs = DEFAULT_REQUEST_TIMEOUT_MS) {
624
622
  this.closed = true;
@@ -693,8 +691,8 @@ function eventURL(app, name) {
693
691
  if (!name) return `app://${app}`;
694
692
  return `app://${app}/${name.replace(/\./g, "/")}`;
695
693
  }
696
- function normalizePagePath(path5) {
697
- const trimmed = String(path5 || "").trim();
694
+ function normalizePagePath(path4) {
695
+ const trimmed = String(path4 || "").trim();
698
696
  if (!trimmed) return "/";
699
697
  return trimmed.startsWith("/") ? trimmed : `/${trimmed}`;
700
698
  }
@@ -742,11 +740,13 @@ async function run(argv) {
742
740
  }
743
741
  }
744
742
  function collapseHomeDirectory(file, home) {
745
- if (file === home) {
743
+ const normalizedFile = file.replaceAll("\\", "/");
744
+ const normalizedHome = home.replaceAll("\\", "/");
745
+ if (normalizedFile === normalizedHome) {
746
746
  return "~";
747
747
  }
748
- if (file.startsWith(`${home}${path4.sep}`)) {
749
- return `~${file.slice(home.length)}`;
748
+ if (normalizedFile.startsWith(`${normalizedHome}/`)) {
749
+ return `~/${normalizedFile.slice(normalizedHome.length + 1)}`;
750
750
  }
751
751
  return file;
752
752
  }
@@ -774,8 +774,9 @@ function createHomeOutput({ bin, sessions }) {
774
774
  url: session.url,
775
775
  pending_prompts: session.pending_prompts || 0
776
776
  })),
777
- use_cases: [
778
- "Implementation plans with diagrams, mockups, data flow, and code snippets",
777
+ example_use_cases: [
778
+ "Technical plans with diagrams, mockups, data flow, and code snippets",
779
+ "Brainstorming early ideas with recommendations, open questions, and visible tradeoffs",
779
780
  "Design explorations with multiple visual options side by side",
780
781
  "PR and code review explainers with annotated diffs and findings",
781
782
  "Interactive prototypes with sliders, knobs, forms, and animation tuning",
@@ -783,6 +784,7 @@ function createHomeOutput({ bin, sessions }) {
783
784
  "Custom editing interfaces for triage, config editing, prompt tuning, dataset curation, and structured config editing"
784
785
  ],
785
786
  artifact_guidance: [
787
+ "Unless the user specifies another location, create HTML artifacts in the current working directory under `.lavish/`",
786
788
  "Use clear sections, headings, tabs, cards, tables, diagrams, and spatial layout instead of long prose",
787
789
  "Include concrete artifacts such as mockups, SVG diagrams, annotated code snippets, diffs, data-flow charts, and examples",
788
790
  "For exploration, show multiple options side by side and label the tradeoff each option makes",
package/package.json CHANGED
@@ -1,18 +1,20 @@
1
1
  {
2
2
  "name": "lavish-axi",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "description": "HTML is the new markdown. Lavish is the new editor for your HTML artifacts.",
5
5
  "type": "module",
6
6
  "bin": {
7
- "lavish-axi": "./dist/cli.mjs"
7
+ "lavish-axi": "dist/cli.mjs"
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
+ "lavish-editor-marketing/renders/lavish-editor-marketing.gif",
11
12
  "LICENSE",
12
13
  "README.md"
13
14
  ],
14
15
  "scripts": {
15
16
  "build": "node scripts/build.js",
17
+ "check": "npm run build && npm run lint && npm run format:check && npm run typecheck && npm test",
16
18
  "start": "node dist/cli.mjs",
17
19
  "lint": "eslint bin src test scripts",
18
20
  "format": "prettier --write .",
@@ -51,12 +53,12 @@
51
53
  "license": "MIT",
52
54
  "repository": {
53
55
  "type": "git",
54
- "url": "https://github.com/kunchenguid/lavish.git"
56
+ "url": "git+https://github.com/kunchenguid/lavish-axi.git"
55
57
  },
56
58
  "bugs": {
57
- "url": "https://github.com/kunchenguid/lavish/issues"
59
+ "url": "https://github.com/kunchenguid/lavish-axi/issues"
58
60
  },
59
- "homepage": "https://github.com/kunchenguid/lavish#readme",
61
+ "homepage": "https://github.com/kunchenguid/lavish-axi#readme",
60
62
  "publishConfig": {
61
63
  "access": "public"
62
64
  }