Add live variant mode: element picker, action panel, poll/reply bridge (22 commands)

New feature: /impeccable live starts an interactive visual iteration server.
Users select elements in the browser, pick a design action (bolder, quieter,
etc.), and the agent generates HTML+CSS variants written directly to source.
The dev server's HMR hot-swaps them in, and MutationObserver progressively
reveals each variant in a cycler UI as it arrives.

Architecture:
- src/live/server.mjs: HTTP + WebSocket server with session token auth,
  long-poll /poll endpoint for the agent, WebSocket for the browser
- src/live/poll.mjs: CLI client (npx impeccable poll / poll --reply)
- src/live/browser.js: element picker with keyboard nav (arrows=siblings,
  shift+arrows=parent/child), action panel (12 commands, freeform input,
  variant count), variant cycler with progressive reveal via MutationObserver
- src/live/protocol.mjs: shared message types and event validation
- source/skills/impeccable/reference/live.md: agent loop instructions
  (inject script, poll loop, generate variants, accept/discard, cleanup)

CLI changes:
- bin/cli.js: added "poll" top-level command
- src/detect-antipatterns.mjs: liveCli() now delegates to src/live/server.mjs
- package.json: added ws dependency

Registered /impeccable live as command #22 across all standard locations.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-04-12 17:13:53 -07:00
co-authored by Claude Opus 4.6
parent e58cbc432f
commit bb94dadda0
67 changed files with 4136 additions and 164 deletions
+2 -111
View File
@@ -3446,118 +3446,9 @@ async function main() {
// Live detection server
// ---------------------------------------------------------------------------
async function findOpenPort(start = 8400) {
const net = await import('node:net');
return new Promise((resolve) => {
const server = net.default.createServer();
server.listen(start, '127.0.0.1', () => {
const port = server.address().port;
server.close(() => resolve(port));
});
server.on('error', () => resolve(findOpenPort(start + 1)));
});
}
const LIVE_PID_FILE = path.join((await import('node:os')).default.tmpdir(), 'impeccable-live.json');
async function liveCli() {
const args = process.argv.slice(2);
const helpMode = args.includes('--help');
const stopMode = args.includes('stop');
const portArg = args.find(a => a.startsWith('--port='));
const requestedPort = portArg ? parseInt(portArg.split('=')[1], 10) : null;
if (helpMode) {
console.log(`Usage: impeccable live [options]
Start a local server that serves the browser detection overlay script.
Inject the script into any page to scan for anti-patterns in real time.
Commands:
live Start the server (default)
live stop Stop a running live server
Options:
--port=PORT Use a specific port (default: auto-detect unused port)
--help Show this help message
The server provides:
/detect.js The detection overlay script (inject via <script> tag)
/health Health check endpoint
/stop Stop the server remotely`);
process.exit(0);
}
// Stop a running server
if (stopMode) {
try {
const info = JSON.parse(fs.readFileSync(LIVE_PID_FILE, 'utf-8'));
const res = await fetch(`http://localhost:${info.port}/stop`);
if (res.ok) {
console.log(`Stopped live server on port ${info.port}.`);
}
} catch {
console.log('No running live server found.');
}
process.exit(0);
}
const http = await import('node:http');
const scriptPath = path.join(path.dirname(new URL(import.meta.url).pathname), 'detect-antipatterns-browser.js');
let browserScript;
try {
browserScript = fs.readFileSync(scriptPath, 'utf-8');
} catch {
process.stderr.write('Error: Browser script not found. Run `npm run build:browser` first.\n');
process.exit(1);
}
const port = requestedPort || await findOpenPort();
const shutdown = () => {
try { fs.unlinkSync(LIVE_PID_FILE); } catch { /* ignore */ }
server.close();
process.exit(0);
};
const server = http.default.createServer((req, res) => {
// CORS headers for cross-origin injection
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
if (req.url === '/detect.js' || req.url === '/') {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(browserScript);
} else if (req.url === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ status: 'ok', port }));
} else if (req.url === '/stop') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('stopping');
shutdown();
} else {
res.writeHead(404);
res.end('Not found');
}
});
server.listen(port, '127.0.0.1', () => {
// Write PID file so `live stop` can find us
fs.writeFileSync(LIVE_PID_FILE, JSON.stringify({ pid: process.pid, port }));
const url = `http://localhost:${port}`;
console.log(`Impeccable live detection server running on ${url}\n`);
console.log(`Inject into any page:`);
console.log(` const s = document.createElement('script');`);
console.log(` s.src = '${url}/detect.js';`);
console.log(` document.head.appendChild(s);\n`);
console.log(`Stop: npx impeccable live stop`);
});
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
const { startLiveServer } = await import('./live/server.mjs');
await startLiveServer();
}
// ---------------------------------------------------------------------------
+931
View File
@@ -0,0 +1,931 @@
/**
* Impeccable Live Variant Mode — Browser Script
*
* Injected into the user's page via <script src="http://localhost:PORT/live.js">.
* The server prepends window.__IMPECCABLE_TOKEN__ and window.__IMPECCABLE_PORT__
* before this code.
*
* Features:
* - Element picker with hover highlight and keyboard navigation
* - Action panel (command dropdown, freeform input, variant count, go button)
* - Variant cycler with MutationObserver progressive reveal
* - WebSocket connection to the live server
*/
(function () {
'use strict';
if (typeof window === 'undefined') return;
const TOKEN = window.__IMPECCABLE_TOKEN__;
const PORT = window.__IMPECCABLE_PORT__;
if (!TOKEN || !PORT) {
console.warn('[impeccable] Live script loaded without token/port. Aborting.');
return;
}
// Prevent double-init
if (window.__IMPECCABLE_LIVE_INIT__) return;
window.__IMPECCABLE_LIVE_INIT__ = true;
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const BRAND = 'oklch(55% 0.25 350)';
const BRAND_HOVER = 'oklch(45% 0.25 350)';
const BRAND_LIGHT = 'oklch(92% 0.06 350)';
const INK = 'oklch(15% 0.01 350)';
const ASH = 'oklch(55% 0 0)';
const PAPER = 'oklch(98% 0.005 350)';
const MIST = 'oklch(90% 0.01 350)';
const FONT = 'system-ui, -apple-system, sans-serif';
const MONO = 'ui-monospace, SFMono-Regular, Menlo, monospace';
const Z_HIGHLIGHT = 99990;
const Z_PANEL = 99995;
const Z_CYCLER = 99995;
const PREFIX = 'impeccable-live';
const SKIP_TAGS = new Set([
'html', 'head', 'body', 'script', 'style', 'link', 'meta',
'noscript', 'br', 'wbr',
]);
const VISUAL_ACTIONS = [
{ value: 'impeccable', label: 'Auto (freeform)' },
{ value: 'bolder', label: 'Bolder' },
{ value: 'quieter', label: 'Quieter' },
{ value: 'distill', label: 'Distill' },
{ value: 'polish', label: 'Polish' },
{ value: 'typeset', label: 'Typeset' },
{ value: 'colorize', label: 'Colorize' },
{ value: 'layout', label: 'Layout' },
{ value: 'adapt', label: 'Adapt' },
{ value: 'animate', label: 'Animate' },
{ value: 'delight', label: 'Delight' },
{ value: 'overdrive', label: 'Overdrive' },
];
// ---------------------------------------------------------------------------
// State
// ---------------------------------------------------------------------------
let state = 'IDLE'; // IDLE | PICKING | CONFIGURING | GENERATING | CYCLING
let ws = null;
let hoveredElement = null;
let selectedElement = null;
let currentSessionId = null;
let expectedVariants = 0;
let arrivedVariants = 0;
let visibleVariant = 0;
let variantObserver = null;
let hasProjectContext = false;
// UI elements
let highlightOverlay = null;
let infoTooltip = null;
let panelEl = null;
let cyclerEl = null;
let toastEl = null;
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function isOwnElement(el) {
if (!el || !el.id) return false;
return el.id.startsWith(PREFIX) || el.closest('[id^="' + PREFIX + '"]');
}
function isPickable(el) {
if (!el || el.nodeType !== 1) return false;
if (SKIP_TAGS.has(el.tagName.toLowerCase())) return false;
if (isOwnElement(el)) return false;
const rect = el.getBoundingClientRect();
if (rect.width < 20 || rect.height < 20) return false;
return true;
}
function elDescriptor(el) {
if (!el) return '';
let s = el.tagName.toLowerCase();
if (el.id) s += '#' + el.id;
if (el.classList.length) s += '.' + [...el.classList].slice(0, 3).join('.');
return s;
}
function genId() {
return crypto.randomUUID().replace(/-/g, '').slice(0, 8);
}
function setState(newState) {
state = newState;
}
// ---------------------------------------------------------------------------
// Highlight overlay
// ---------------------------------------------------------------------------
function createHighlight() {
const el = document.createElement('div');
el.id = PREFIX + '-highlight';
Object.assign(el.style, {
position: 'fixed', top: '0', left: '0', width: '0', height: '0',
border: '2px solid ' + BRAND,
borderRadius: '3px',
pointerEvents: 'none',
zIndex: Z_HIGHLIGHT,
transition: 'top 0.08s ease, left 0.08s ease, width 0.08s ease, height 0.08s ease',
display: 'none',
boxSizing: 'border-box',
});
document.body.appendChild(el);
return el;
}
function createInfoTooltip() {
const el = document.createElement('div');
el.id = PREFIX + '-info';
Object.assign(el.style, {
position: 'fixed', top: '0', left: '0',
background: INK, color: PAPER,
fontFamily: MONO, fontSize: '11px',
padding: '2px 6px', borderRadius: '3px',
zIndex: Z_HIGHLIGHT + 1,
pointerEvents: 'none',
whiteSpace: 'nowrap',
display: 'none',
});
document.body.appendChild(el);
return el;
}
function positionHighlight(el) {
if (!el || !highlightOverlay) return;
const rect = el.getBoundingClientRect();
Object.assign(highlightOverlay.style, {
top: (rect.top - 2) + 'px',
left: (rect.left - 2) + 'px',
width: (rect.width + 4) + 'px',
height: (rect.height + 4) + 'px',
display: 'block',
});
if (infoTooltip) {
infoTooltip.textContent = elDescriptor(el);
const tipTop = rect.top - 22;
Object.assign(infoTooltip.style, {
top: (tipTop < 4 ? rect.bottom + 4 : tipTop) + 'px',
left: Math.max(4, rect.left) + 'px',
display: 'block',
});
}
}
function hideHighlight() {
if (highlightOverlay) highlightOverlay.style.display = 'none';
if (infoTooltip) infoTooltip.style.display = 'none';
}
// ---------------------------------------------------------------------------
// Element context extraction (sent to agent)
// ---------------------------------------------------------------------------
function extractElementContext(el) {
const computed = getComputedStyle(el);
const rect = el.getBoundingClientRect();
// Collect CSS custom properties from stylesheets
const customProps = {};
for (const sheet of document.styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (rule.style) {
for (let i = 0; i < rule.style.length; i++) {
const prop = rule.style[i];
if (prop.startsWith('--') && !customProps[prop]) {
const val = computed.getPropertyValue(prop).trim();
if (val) customProps[prop] = val;
}
}
}
}
} catch { /* cross-origin sheet */ }
}
return {
tagName: el.tagName.toLowerCase(),
id: el.id || null,
classes: [...el.classList],
textContent: (el.textContent || '').slice(0, 500),
outerHTML: el.outerHTML.slice(0, 10000),
computedStyles: {
'font-family': computed.fontFamily,
'font-size': computed.fontSize,
'font-weight': computed.fontWeight,
'line-height': computed.lineHeight,
'color': computed.color,
'background': computed.background,
'background-color': computed.backgroundColor,
'padding': computed.padding,
'margin': computed.margin,
'display': computed.display,
'position': computed.position,
'gap': computed.gap,
'border-radius': computed.borderRadius,
'box-shadow': computed.boxShadow,
},
cssCustomProperties: customProps,
parentContext: el.parentElement ?
'<' + el.parentElement.tagName.toLowerCase() +
(el.parentElement.id ? ' id="' + el.parentElement.id + '"' : '') +
(el.parentElement.className ? ' class="' + el.parentElement.className + '"' : '') +
'>' : null,
boundingRect: {
width: Math.round(rect.width),
height: Math.round(rect.height),
},
};
}
// ---------------------------------------------------------------------------
// Action panel
// ---------------------------------------------------------------------------
function createActionPanel() {
const panel = document.createElement('div');
panel.id = PREFIX + '-panel';
Object.assign(panel.style, {
position: 'fixed', zIndex: Z_PANEL,
background: PAPER,
border: '1px solid ' + MIST,
borderRadius: '10px',
padding: '14px 16px',
boxShadow: '0 4px 24px oklch(0% 0 0 / 0.12)',
fontFamily: FONT,
fontSize: '13px',
color: INK,
minWidth: '260px',
maxWidth: '320px',
display: 'none',
});
// Header
const header = document.createElement('div');
Object.assign(header.style, {
display: 'flex', alignItems: 'center', gap: '6px',
marginBottom: '10px', fontSize: '12px', color: ASH,
});
const brand = document.createElement('span');
brand.textContent = 'impeccable';
Object.assign(brand.style, { fontWeight: '600', color: BRAND, fontFamily: MONO, fontSize: '11px' });
header.appendChild(brand);
const elLabel = document.createElement('span');
elLabel.id = PREFIX + '-panel-label';
Object.assign(elLabel.style, { fontFamily: MONO, fontSize: '11px' });
header.appendChild(elLabel);
panel.appendChild(header);
// Action select
const actionRow = document.createElement('div');
Object.assign(actionRow.style, { marginBottom: '8px' });
const actionLabel = document.createElement('label');
actionLabel.textContent = 'Action';
Object.assign(actionLabel.style, {
display: 'block', fontSize: '11px', fontWeight: '500',
color: ASH, marginBottom: '3px', textTransform: 'uppercase',
letterSpacing: '0.06em',
});
actionRow.appendChild(actionLabel);
const actionSelect = document.createElement('select');
actionSelect.id = PREFIX + '-action';
Object.assign(actionSelect.style, {
width: '100%', padding: '6px 8px', borderRadius: '5px',
border: '1px solid ' + MIST, fontFamily: FONT, fontSize: '13px',
background: '#fff', color: INK, cursor: 'pointer',
});
VISUAL_ACTIONS.forEach(a => {
const opt = document.createElement('option');
opt.value = a.value;
opt.textContent = a.label;
actionSelect.appendChild(opt);
});
actionRow.appendChild(actionSelect);
panel.appendChild(actionRow);
// Freeform input
const freeformRow = document.createElement('div');
Object.assign(freeformRow.style, { marginBottom: '8px' });
const freeformLabel = document.createElement('label');
freeformLabel.textContent = 'Instructions (optional)';
Object.assign(freeformLabel.style, {
display: 'block', fontSize: '11px', fontWeight: '500',
color: ASH, marginBottom: '3px', textTransform: 'uppercase',
letterSpacing: '0.06em',
});
freeformRow.appendChild(freeformLabel);
const freeformInput = document.createElement('input');
freeformInput.id = PREFIX + '-freeform';
freeformInput.type = 'text';
freeformInput.placeholder = 'e.g. make it feel more premium';
Object.assign(freeformInput.style, {
width: '100%', padding: '6px 8px', borderRadius: '5px',
border: '1px solid ' + MIST, fontFamily: FONT, fontSize: '13px',
boxSizing: 'border-box',
});
freeformRow.appendChild(freeformInput);
panel.appendChild(freeformRow);
// Variant count
const countRow = document.createElement('div');
Object.assign(countRow.style, {
display: 'flex', alignItems: 'center', gap: '6px',
marginBottom: '12px',
});
const countLabel = document.createElement('span');
countLabel.textContent = 'Variants';
Object.assign(countLabel.style, {
fontSize: '11px', fontWeight: '500', color: ASH,
textTransform: 'uppercase', letterSpacing: '0.06em',
});
countRow.appendChild(countLabel);
[2, 3, 4].forEach(n => {
const btn = document.createElement('button');
btn.textContent = n;
btn.dataset.count = n;
btn.className = PREFIX + '-count-btn';
Object.assign(btn.style, {
width: '32px', height: '28px', borderRadius: '5px',
border: '1px solid ' + MIST, background: n === 3 ? BRAND : '#fff',
color: n === 3 ? '#fff' : INK, fontFamily: FONT, fontSize: '13px',
fontWeight: '500', cursor: 'pointer', transition: 'all 0.1s ease',
});
btn.addEventListener('click', () => {
document.querySelectorAll('.' + PREFIX + '-count-btn').forEach(b => {
Object.assign(b.style, { background: '#fff', color: INK });
});
Object.assign(btn.style, { background: BRAND, color: '#fff' });
});
countRow.appendChild(btn);
});
panel.appendChild(countRow);
// Go + Cancel row
const btnRow = document.createElement('div');
Object.assign(btnRow.style, { display: 'flex', alignItems: 'center', gap: '8px' });
const goBtn = document.createElement('button');
goBtn.id = PREFIX + '-go';
goBtn.textContent = 'Generate';
Object.assign(goBtn.style, {
flex: '1', padding: '8px 16px', borderRadius: '5px',
border: 'none', background: INK, color: '#fff',
fontFamily: FONT, fontSize: '13px', fontWeight: '600',
cursor: 'pointer', transition: 'background 0.15s ease',
});
goBtn.addEventListener('mouseenter', () => { goBtn.style.background = BRAND; });
goBtn.addEventListener('mouseleave', () => { goBtn.style.background = INK; });
goBtn.addEventListener('click', handleGo);
btnRow.appendChild(goBtn);
const cancelLink = document.createElement('button');
cancelLink.textContent = 'Cancel';
Object.assign(cancelLink.style, {
background: 'none', border: 'none', color: ASH,
fontFamily: FONT, fontSize: '12px', cursor: 'pointer',
padding: '4px',
});
cancelLink.addEventListener('click', () => {
hidePanel();
setState('PICKING');
});
btnRow.appendChild(cancelLink);
panel.appendChild(btnRow);
document.body.appendChild(panel);
return panel;
}
function showPanel(el) {
if (!panelEl) panelEl = createActionPanel();
const label = panelEl.querySelector('#' + PREFIX + '-panel-label');
if (label) label.textContent = elDescriptor(el);
// Position near element
const rect = el.getBoundingClientRect();
const panelWidth = 300;
const panelHeight = 280;
let top = rect.bottom + 8;
let left = rect.right - panelWidth;
if (left < 8) left = 8;
if (top + panelHeight > window.innerHeight) top = rect.top - panelHeight - 8;
if (top < 8) top = 8;
if (left + panelWidth > window.innerWidth) left = window.innerWidth - panelWidth - 8;
Object.assign(panelEl.style, {
top: top + 'px', left: left + 'px', display: 'block',
});
// Focus the freeform input
const input = panelEl.querySelector('#' + PREFIX + '-freeform');
if (input) setTimeout(() => input.focus(), 50);
}
function hidePanel() {
if (panelEl) panelEl.style.display = 'none';
}
function getSelectedAction() {
const select = document.querySelector('#' + PREFIX + '-action');
return select ? select.value : 'impeccable';
}
function getSelectedCount() {
const active = document.querySelector('.' + PREFIX + '-count-btn[style*="' + BRAND + '"]');
return active ? parseInt(active.dataset.count) : 3;
}
function getFreeformPrompt() {
const input = document.querySelector('#' + PREFIX + '-freeform');
return input ? input.value.trim() : '';
}
// ---------------------------------------------------------------------------
// Variant cycler
// ---------------------------------------------------------------------------
function createCycler() {
const el = document.createElement('div');
el.id = PREFIX + '-cycler';
Object.assign(el.style, {
position: 'fixed', zIndex: Z_CYCLER,
background: PAPER,
border: '1px solid ' + MIST,
borderRadius: '10px',
padding: '10px 14px',
boxShadow: '0 4px 24px oklch(0% 0 0 / 0.12)',
fontFamily: FONT, fontSize: '13px', color: INK,
display: 'none',
minWidth: '280px',
});
// Status line
const statusRow = document.createElement('div');
statusRow.id = PREFIX + '-cycler-status';
Object.assign(statusRow.style, {
textAlign: 'center', marginBottom: '8px',
fontSize: '12px', color: ASH,
});
el.appendChild(statusRow);
// Slot indicators
const slotsRow = document.createElement('div');
slotsRow.id = PREFIX + '-cycler-slots';
Object.assign(slotsRow.style, {
display: 'flex', justifyContent: 'center', gap: '4px',
marginBottom: '10px',
});
el.appendChild(slotsRow);
// Navigation
const navRow = document.createElement('div');
Object.assign(navRow.style, {
display: 'flex', alignItems: 'center', justifyContent: 'center', gap: '8px',
});
const prevBtn = document.createElement('button');
prevBtn.id = PREFIX + '-cycler-prev';
prevBtn.textContent = '\u2190';
const nextBtn = document.createElement('button');
nextBtn.id = PREFIX + '-cycler-next';
nextBtn.textContent = '\u2192';
[prevBtn, nextBtn].forEach(b => {
Object.assign(b.style, {
width: '32px', height: '28px', borderRadius: '5px',
border: '1px solid ' + MIST, background: '#fff', color: INK,
fontFamily: FONT, fontSize: '14px', cursor: 'pointer',
});
});
const acceptBtn = document.createElement('button');
acceptBtn.id = PREFIX + '-cycler-accept';
acceptBtn.textContent = 'Accept';
Object.assign(acceptBtn.style, {
padding: '6px 16px', borderRadius: '5px',
border: 'none', background: INK, color: '#fff',
fontFamily: FONT, fontSize: '12px', fontWeight: '600',
cursor: 'pointer',
});
const discardBtn = document.createElement('button');
discardBtn.id = PREFIX + '-cycler-discard';
discardBtn.textContent = 'Discard';
Object.assign(discardBtn.style, {
padding: '6px 12px', borderRadius: '5px',
border: '1px solid ' + MIST, background: '#fff', color: ASH,
fontFamily: FONT, fontSize: '12px', cursor: 'pointer',
});
prevBtn.addEventListener('click', () => cycleVariant(-1));
nextBtn.addEventListener('click', () => cycleVariant(1));
acceptBtn.addEventListener('click', handleAccept);
discardBtn.addEventListener('click', handleDiscard);
navRow.appendChild(prevBtn);
navRow.appendChild(acceptBtn);
navRow.appendChild(discardBtn);
navRow.appendChild(nextBtn);
el.appendChild(navRow);
document.body.appendChild(el);
return el;
}
function showCycler() {
if (!cyclerEl) cyclerEl = createCycler();
// Position near the selected element (or its variant wrapper)
const wrapper = document.querySelector('[data-impeccable-variants="' + currentSessionId + '"]');
const target = wrapper || selectedElement;
if (target) {
const rect = target.getBoundingClientRect();
let top = rect.bottom + 8;
if (top + 80 > window.innerHeight) top = rect.top - 88;
if (top < 8) top = 8;
const left = Math.max(8, Math.min(rect.left, window.innerWidth - 300));
Object.assign(cyclerEl.style, { top: top + 'px', left: left + 'px' });
}
cyclerEl.style.display = 'block';
updateCyclerUI();
}
function hideCycler() {
if (cyclerEl) cyclerEl.style.display = 'none';
if (variantObserver) {
variantObserver.disconnect();
variantObserver = null;
}
}
function updateCyclerUI() {
if (!cyclerEl) return;
const statusEl = cyclerEl.querySelector('#' + PREFIX + '-cycler-status');
const slotsEl = cyclerEl.querySelector('#' + PREFIX + '-cycler-slots');
// Update slots
slotsEl.innerHTML = '';
for (let i = 1; i <= expectedVariants; i++) {
const dot = document.createElement('div');
const arrived = i <= arrivedVariants;
const active = i === visibleVariant;
Object.assign(dot.style, {
width: '8px', height: '8px', borderRadius: '50%',
background: active ? BRAND : (arrived ? MIST : 'transparent'),
border: '1.5px solid ' + (arrived ? BRAND : MIST),
cursor: arrived ? 'pointer' : 'default',
transition: 'all 0.15s ease',
});
if (arrived) {
dot.addEventListener('click', () => {
visibleVariant = i;
showVariantInDOM(currentSessionId, i);
updateCyclerUI();
});
}
slotsEl.appendChild(dot);
}
// Update status text
if (arrivedVariants < expectedVariants) {
statusEl.textContent = 'Generating variant ' + (arrivedVariants + 1) + ' of ' + expectedVariants + '...';
} else {
statusEl.textContent = 'Variant ' + visibleVariant + ' of ' + expectedVariants;
}
// Enable/disable nav buttons
const prev = cyclerEl.querySelector('#' + PREFIX + '-cycler-prev');
const next = cyclerEl.querySelector('#' + PREFIX + '-cycler-next');
const accept = cyclerEl.querySelector('#' + PREFIX + '-cycler-accept');
if (prev) prev.disabled = visibleVariant <= 1;
if (next) next.disabled = visibleVariant >= arrivedVariants;
if (accept) accept.disabled = arrivedVariants === 0;
}
function cycleVariant(dir) {
const newV = visibleVariant + dir;
if (newV < 1 || newV > arrivedVariants) return;
visibleVariant = newV;
showVariantInDOM(currentSessionId, newV);
updateCyclerUI();
}
function showVariantInDOM(sessionId, num) {
const wrapper = document.querySelector('[data-impeccable-variants="' + sessionId + '"]');
if (!wrapper) return;
for (const child of wrapper.children) {
const v = child.dataset ? child.dataset.impeccableVariant : null;
if (!v) continue;
child.style.display = (v === String(num)) ? '' : 'none';
}
}
// ---------------------------------------------------------------------------
// MutationObserver for progressive variant reveal
// ---------------------------------------------------------------------------
function startVariantObserver(sessionId) {
// Watch the entire body for [data-impeccable-variants] appearing (after HMR)
const bodyObserver = new MutationObserver(() => {
const wrapper = document.querySelector('[data-impeccable-variants="' + sessionId + '"]');
if (!wrapper) return;
// Count arrived variants
const variants = wrapper.querySelectorAll('[data-impeccable-variant]:not([data-impeccable-variant="original"])');
const newCount = variants.length;
if (newCount > arrivedVariants) {
arrivedVariants = newCount;
// Auto-show the first variant
if (visibleVariant === 0 && arrivedVariants > 0) {
visibleVariant = 1;
showVariantInDOM(sessionId, 1);
}
updateCyclerUI();
}
// Read expected count from the wrapper attribute
const expected = parseInt(wrapper.dataset.impeccableVariantCount || '0');
if (expected > 0 && expected !== expectedVariants) {
expectedVariants = expected;
updateCyclerUI();
}
// If all variants have arrived, transition to CYCLING
if (arrivedVariants >= expectedVariants && expectedVariants > 0) {
setState('CYCLING');
}
});
bodyObserver.observe(document.body, { childList: true, subtree: true });
return bodyObserver;
}
// ---------------------------------------------------------------------------
// WebSocket
// ---------------------------------------------------------------------------
function connectWS() {
ws = new WebSocket('ws://localhost:' + PORT + '/ws');
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'auth', token: TOKEN }));
};
ws.onmessage = (event) => {
let msg;
try { msg = JSON.parse(event.data); } catch { return; }
switch (msg.type) {
case 'auth_ok':
hasProjectContext = !!msg.hasProjectContext;
if (!hasProjectContext) showContextWarning();
console.log('[impeccable] Live mode connected.');
setState('PICKING');
break;
case 'auth_fail':
console.error('[impeccable] Auth failed:', msg.reason);
break;
case 'generating':
// Agent acknowledged the generate request
break;
case 'done':
// Agent finished writing all variants
setState('CYCLING');
updateCyclerUI();
break;
case 'error':
console.error('[impeccable] Error:', msg.message);
showError(msg.message);
hideCycler();
setState('PICKING');
break;
}
};
ws.onclose = () => {
console.log('[impeccable] WebSocket closed. Reconnecting in 3s...');
setTimeout(connectWS, 3000);
};
ws.onerror = () => {
// onclose will fire after this
};
}
function sendWS(msg) {
if (ws && ws.readyState === 1) {
ws.send(JSON.stringify(msg));
}
}
// ---------------------------------------------------------------------------
// Event handlers
// ---------------------------------------------------------------------------
function handleMouseMove(e) {
if (state !== 'PICKING') return;
const el = document.elementFromPoint(e.clientX, e.clientY);
if (!el || !isPickable(el) || el === hoveredElement) return;
hoveredElement = el;
positionHighlight(el);
}
function handleClick(e) {
if (state !== 'PICKING') return;
if (isOwnElement(e.target)) return;
const el = hoveredElement;
if (!el || !isPickable(el)) return;
e.preventDefault();
e.stopPropagation();
selectedElement = el;
setState('CONFIGURING');
positionHighlight(el);
showPanel(el);
}
function handleKeyDown(e) {
// ESC: cancel current state
if (e.key === 'Escape') {
e.preventDefault();
if (state === 'CONFIGURING') {
hidePanel();
setState('PICKING');
} else if (state === 'CYCLING') {
handleDiscard();
} else if (state === 'PICKING') {
hideHighlight();
hidePanel();
hideCycler();
setState('IDLE');
}
return;
}
// Arrow keys: navigate elements
if (state === 'PICKING' && hoveredElement) {
let next = null;
if (e.key === 'ArrowDown' && !e.shiftKey) {
next = hoveredElement.nextElementSibling;
while (next && !isPickable(next)) next = next.nextElementSibling;
} else if (e.key === 'ArrowUp' && !e.shiftKey) {
next = hoveredElement.previousElementSibling;
while (next && !isPickable(next)) next = next.previousElementSibling;
} else if (e.key === 'ArrowUp' && e.shiftKey) {
next = hoveredElement.parentElement;
if (next && !isPickable(next)) next = null;
} else if (e.key === 'ArrowDown' && e.shiftKey) {
next = hoveredElement.firstElementChild;
while (next && !isPickable(next)) next = next.nextElementSibling;
} else if (e.key === 'Enter') {
e.preventDefault();
selectedElement = hoveredElement;
setState('CONFIGURING');
positionHighlight(hoveredElement);
showPanel(hoveredElement);
return;
}
if (next) {
e.preventDefault();
hoveredElement = next;
positionHighlight(next);
next.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
}
return;
}
// Variant cycling keys
if (state === 'CYCLING') {
if (e.key === 'ArrowLeft') { e.preventDefault(); cycleVariant(-1); }
if (e.key === 'ArrowRight') { e.preventDefault(); cycleVariant(1); }
if (e.key === 'Enter') { e.preventDefault(); handleAccept(); }
}
}
function handleGo() {
if (!selectedElement || state !== 'CONFIGURING') return;
const action = getSelectedAction();
const count = getSelectedCount();
const freeformPrompt = getFreeformPrompt();
currentSessionId = genId();
expectedVariants = count;
arrivedVariants = 0;
visibleVariant = 0;
// Send generate event to server
sendWS({
type: 'generate',
id: currentSessionId,
action: action,
freeformPrompt: freeformPrompt || undefined,
count: count,
element: extractElementContext(selectedElement),
});
hidePanel();
setState('GENERATING');
// Start observing for variants in the DOM
if (variantObserver) variantObserver.disconnect();
variantObserver = startVariantObserver(currentSessionId);
// Show cycler in "generating" state
showCycler();
}
function handleAccept() {
if (!currentSessionId || arrivedVariants === 0) return;
sendWS({
type: 'accept',
id: currentSessionId,
variantId: String(visibleVariant),
});
hideCycler();
hideHighlight();
selectedElement = null;
currentSessionId = null;
setState('PICKING');
}
function handleDiscard() {
if (!currentSessionId) return;
sendWS({
type: 'discard',
id: currentSessionId,
});
hideCycler();
hideHighlight();
selectedElement = null;
currentSessionId = null;
setState('PICKING');
}
// ---------------------------------------------------------------------------
// Toast / warnings / errors
// ---------------------------------------------------------------------------
function showContextWarning() {
showToast('Running without project context. Run /impeccable teach for better variants.', 8000);
}
function showError(message) {
showToast('Error: ' + message, 6000);
}
function showToast(message, duration) {
if (toastEl) toastEl.remove();
toastEl = document.createElement('div');
toastEl.id = PREFIX + '-toast';
toastEl.textContent = message;
Object.assign(toastEl.style, {
position: 'fixed', bottom: '16px', left: '50%',
transform: 'translateX(-50%)',
background: INK, color: PAPER,
fontFamily: FONT, fontSize: '12px',
padding: '8px 16px', borderRadius: '6px',
zIndex: Z_PANEL + 10,
opacity: '0',
transition: 'opacity 0.3s ease',
pointerEvents: 'none',
maxWidth: '500px', textAlign: 'center',
});
document.body.appendChild(toastEl);
requestAnimationFrame(() => { toastEl.style.opacity = '1'; });
setTimeout(() => {
if (toastEl) {
toastEl.style.opacity = '0';
setTimeout(() => { if (toastEl) toastEl.remove(); toastEl = null; }, 300);
}
}, duration);
}
// ---------------------------------------------------------------------------
// Init
// ---------------------------------------------------------------------------
function init() {
highlightOverlay = createHighlight();
infoTooltip = createInfoTooltip();
document.addEventListener('mousemove', handleMouseMove, true);
document.addEventListener('click', handleClick, true);
document.addEventListener('keydown', handleKeyDown, true);
connectWS();
console.log('[impeccable] Live variant mode ready. Hover over elements to pick one.');
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();
+119
View File
@@ -0,0 +1,119 @@
/**
* CLI client for the live variant mode poll/reply protocol.
*
* Usage:
* npx impeccable poll # Block until browser event, print JSON
* npx impeccable poll --timeout=60000 # Custom timeout (ms)
* npx impeccable poll --reply <id> done # Reply "done" to event <id>
* npx impeccable poll --reply <id> error "msg" # Reply with error
*/
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
const LIVE_PID_FILE = path.join(os.tmpdir(), 'impeccable-live.json');
function readServerInfo() {
try {
return JSON.parse(fs.readFileSync(LIVE_PID_FILE, 'utf-8'));
} catch {
console.error('No running live server found. Start one with: npx impeccable live');
process.exit(1);
}
}
export async function pollCli() {
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
console.log(`Usage: impeccable poll [options]
Wait for a browser event from the live variant server, or reply to one.
Modes:
poll Block until a browser event arrives, print JSON
poll --reply <id> done Reply "done" to event <id>
poll --reply <id> error "msg" Reply with an error message
Options:
--timeout=MS Poll timeout in milliseconds (default: 120000)
--help Show this help message`);
process.exit(0);
}
const info = readServerInfo();
const base = `http://localhost:${info.port}`;
// Reply mode: npx impeccable poll --reply <id> <status> [message]
const replyIdx = args.indexOf('--reply');
if (replyIdx !== -1) {
const id = args[replyIdx + 1];
const status = args[replyIdx + 2] || 'done';
const message = args[replyIdx + 3] || undefined;
if (!id) {
console.error('Usage: npx impeccable poll --reply <id> <status> [message]');
process.exit(1);
}
try {
const res = await fetch(`${base}/poll`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: info.token,
id,
type: status,
message,
}),
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
console.error(`Reply failed (${res.status}):`, body.error || res.statusText);
process.exit(1);
}
// Success — silent exit (agent doesn't need output for replies)
} catch (err) {
if (err.cause?.code === 'ECONNREFUSED') {
console.error('Live server not running. Start one with: npx impeccable live');
} else {
console.error('Reply failed:', err.message);
}
process.exit(1);
}
return;
}
// Poll mode: block until browser event
const timeoutArg = args.find(a => a.startsWith('--timeout='));
const timeout = timeoutArg ? parseInt(timeoutArg.split('=')[1], 10) : 120000;
try {
const res = await fetch(`${base}/poll?token=${info.token}&timeout=${timeout}`);
if (res.status === 401) {
console.error('Authentication failed. The server token may have changed.');
console.error('Try restarting: npx impeccable live stop && npx impeccable live');
process.exit(1);
}
if (!res.ok) {
console.error(`Poll failed: ${res.status} ${res.statusText}`);
process.exit(1);
}
const event = await res.json();
// Print the event as JSON — the agent reads this from stdout
console.log(JSON.stringify(event));
} catch (err) {
if (err.cause?.code === 'ECONNREFUSED') {
console.error('Live server not running. Start one with: npx impeccable live');
} else {
console.error('Poll failed:', err.message);
}
process.exit(1);
}
}
+79
View File
@@ -0,0 +1,79 @@
/**
* Shared protocol constants and validation for the live variant mode.
* Imported by both server.mjs and poll.mjs.
*/
// Browser → Server event types
export const EVENT = Object.freeze({
GENERATE: 'generate',
ACCEPT: 'accept',
DISCARD: 'discard',
EXIT: 'exit',
});
// Server → Browser message types
export const MSG = Object.freeze({
AUTH_OK: 'auth_ok',
AUTH_FAIL: 'auth_fail',
GENERATING: 'generating',
DONE: 'done',
ERROR: 'error',
});
// Poll return types (superset of EVENT — adds timeout)
export const POLL = Object.freeze({
...EVENT,
TIMEOUT: 'timeout',
});
// Commands that make sense for visual variant generation.
// Shown in the browser action panel dropdown.
export const VISUAL_ACTIONS = Object.freeze([
'impeccable', // default: freeform design pass
'bolder',
'quieter',
'distill',
'polish',
'typeset',
'colorize',
'layout',
'adapt',
'animate',
'delight',
'overdrive',
]);
/**
* Validate a browser event before queuing it for the agent.
* Returns null if valid, or an error string if not.
*/
export function validateEvent(msg) {
if (!msg || typeof msg !== 'object' || !msg.type) {
return 'Missing or invalid message';
}
switch (msg.type) {
case EVENT.GENERATE:
if (!msg.id || typeof msg.id !== 'string') return 'generate: missing id';
if (!msg.action || !VISUAL_ACTIONS.includes(msg.action)) return `generate: invalid action "${msg.action}"`;
if (!Number.isInteger(msg.count) || msg.count < 1 || msg.count > 8) return 'generate: count must be 1-8';
if (!msg.element || typeof msg.element !== 'object') return 'generate: missing element context';
if (!msg.element.outerHTML) return 'generate: element must include outerHTML';
return null;
case EVENT.ACCEPT:
if (!msg.id || typeof msg.id !== 'string') return 'accept: missing id';
if (!msg.variantId) return 'accept: missing variantId';
return null;
case EVENT.DISCARD:
if (!msg.id || typeof msg.id !== 'string') return 'discard: missing id';
return null;
case EVENT.EXIT:
return null;
default:
return `Unknown event type: "${msg.type}"`;
}
}
+465
View File
@@ -0,0 +1,465 @@
/**
* Live variant mode server.
*
* Serves the browser script (/live.js), the detection overlay (/detect.js),
* manages a WebSocket connection to the browser, and exposes HTTP long-poll
* endpoints so the agent CLI can receive events and send replies.
*
* Start: npx impeccable live
* Stop: npx impeccable live stop
* Health: curl http://localhost:PORT/health
*/
import http from 'node:http';
import { createHash, randomUUID } from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import os from 'node:os';
import net from 'node:net';
import { fileURLToPath } from 'node:url';
import { WebSocketServer } from 'ws';
import { validateEvent } from './protocol.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const LIVE_PID_FILE = path.join(os.tmpdir(), 'impeccable-live.json');
const DEFAULT_POLL_TIMEOUT = 120_000; // 2 minutes
// ---------------------------------------------------------------------------
// Port detection
// ---------------------------------------------------------------------------
async function findOpenPort(start = 8400) {
return new Promise((resolve) => {
const srv = net.createServer();
srv.listen(start, '127.0.0.1', () => {
const port = srv.address().port;
srv.close(() => resolve(port));
});
srv.on('error', () => resolve(findOpenPort(start + 1)));
});
}
// ---------------------------------------------------------------------------
// Session state
// ---------------------------------------------------------------------------
const state = {
token: null,
port: null,
wsClients: new Set(),
// Queue: browser events waiting for the agent to poll
pendingEvents: [],
// Queue: agent poll response callbacks waiting for browser events
pendingPolls: [],
};
/** Push an event from the browser into the queue or resolve a waiting poll. */
function enqueueEvent(event) {
if (state.pendingPolls.length > 0) {
const resolve = state.pendingPolls.shift();
resolve(event);
} else {
state.pendingEvents.push(event);
}
}
/** Broadcast a message to all authenticated WS clients. */
function broadcast(msg) {
const data = JSON.stringify(msg);
for (const ws of state.wsClients) {
if (ws.readyState === 1 /* OPEN */) {
ws.send(data);
}
}
}
// ---------------------------------------------------------------------------
// Load scripts
// ---------------------------------------------------------------------------
function loadBrowserScripts() {
const detectPath = path.join(__dirname, '..', 'detect-antipatterns-browser.js');
const livePath = path.join(__dirname, 'browser.js');
let detectScript = '';
try {
detectScript = fs.readFileSync(detectPath, 'utf-8');
} catch {
// Detection script is optional for the live variant server
}
let liveScript = '';
try {
liveScript = fs.readFileSync(livePath, 'utf-8');
} catch {
process.stderr.write('Error: Browser live script not found at ' + livePath + '\n');
process.exit(1);
}
return { detectScript, liveScript };
}
// ---------------------------------------------------------------------------
// Check for .impeccable.md
// ---------------------------------------------------------------------------
function hasProjectContext() {
try {
fs.accessSync(path.join(process.cwd(), '.impeccable.md'), fs.constants.R_OK);
return true;
} catch {
return false;
}
}
// ---------------------------------------------------------------------------
// HTTP request handler
// ---------------------------------------------------------------------------
function createRequestHandler({ detectScript, liveScriptWithToken }) {
return (req, res) => {
const url = new URL(req.url, `http://localhost:${state.port}`);
// CORS
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; }
const pathname = url.pathname;
// --- Public endpoints (no auth) ---
if (pathname === '/live.js') {
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(liveScriptWithToken);
return;
}
if (pathname === '/detect.js' || pathname === '/') {
if (!detectScript) {
res.writeHead(404);
res.end('Detection script not available. Run npm run build:browser first.');
return;
}
res.writeHead(200, { 'Content-Type': 'application/javascript' });
res.end(detectScript);
return;
}
if (pathname === '/health') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ok',
port: state.port,
mode: 'variant',
hasProjectContext: hasProjectContext(),
connectedClients: state.wsClients.size,
}));
return;
}
// --- Authenticated endpoints ---
const token = url.searchParams.get('token');
if (pathname === '/stop') {
if (token !== state.token) { res.writeHead(401); res.end('Unauthorized'); return; }
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('stopping');
shutdown();
return;
}
if (pathname === '/poll') {
if (req.method === 'GET') {
handlePollGet(req, res, url);
} else if (req.method === 'POST') {
handlePollPost(req, res);
} else {
res.writeHead(405);
res.end('Method not allowed');
}
return;
}
res.writeHead(404);
res.end('Not found');
};
}
// ---------------------------------------------------------------------------
// Poll endpoints
// ---------------------------------------------------------------------------
/** GET /poll — agent blocks here until a browser event arrives. */
function handlePollGet(req, res, url) {
const token = url.searchParams.get('token');
if (token !== state.token) {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Unauthorized' }));
return;
}
const timeout = parseInt(url.searchParams.get('timeout') || DEFAULT_POLL_TIMEOUT, 10);
// If there's already an event queued, return it immediately
if (state.pendingEvents.length > 0) {
const event = state.pendingEvents.shift();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(event));
return;
}
// Otherwise, wait for one
const timer = setTimeout(() => {
// Remove this callback from pendingPolls
const idx = state.pendingPolls.indexOf(resolve);
if (idx !== -1) state.pendingPolls.splice(idx, 1);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ type: 'timeout' }));
}, timeout);
function resolve(event) {
clearTimeout(timer);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(event));
}
state.pendingPolls.push(resolve);
// Clean up if the agent disconnects before we respond
req.on('close', () => {
clearTimeout(timer);
const idx = state.pendingPolls.indexOf(resolve);
if (idx !== -1) state.pendingPolls.splice(idx, 1);
});
}
/** POST /poll — agent replies to a pending browser event. */
function handlePollPost(req, res) {
let body = '';
req.on('data', (chunk) => { body += chunk; });
req.on('end', () => {
let msg;
try {
msg = JSON.parse(body);
} catch {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
return;
}
if (msg.token !== state.token) {
res.writeHead(401, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Unauthorized' }));
return;
}
// Forward the reply to the browser
broadcast({
type: msg.type || 'done',
id: msg.id,
message: msg.message,
data: msg.data,
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
});
}
// ---------------------------------------------------------------------------
// WebSocket handling
// ---------------------------------------------------------------------------
function setupWebSocket(server) {
const wss = new WebSocketServer({ server, path: '/ws' });
wss.on('connection', (ws) => {
let authenticated = false;
ws.on('message', (raw) => {
let msg;
try {
msg = JSON.parse(raw.toString());
} catch {
ws.send(JSON.stringify({ type: 'error', message: 'Invalid JSON' }));
return;
}
// First message must be auth
if (!authenticated) {
if (msg.type === 'auth' && msg.token === state.token) {
authenticated = true;
state.wsClients.add(ws);
ws.send(JSON.stringify({
type: 'auth_ok',
hasProjectContext: hasProjectContext(),
}));
} else {
ws.send(JSON.stringify({ type: 'auth_fail', reason: 'Invalid token' }));
ws.close();
}
return;
}
// Validated browser events go to the agent poll queue
const error = validateEvent(msg);
if (error) {
ws.send(JSON.stringify({ type: 'error', message: error }));
return;
}
enqueueEvent(msg);
});
ws.on('close', () => {
state.wsClients.delete(ws);
// If all browser clients disconnected, signal exit to agent
if (authenticated && state.wsClients.size === 0) {
enqueueEvent({ type: 'exit' });
}
});
ws.on('error', () => {
state.wsClients.delete(ws);
});
});
return wss;
}
// ---------------------------------------------------------------------------
// Lifecycle
// ---------------------------------------------------------------------------
let httpServer = null;
let wss = null;
function shutdown() {
try { fs.unlinkSync(LIVE_PID_FILE); } catch { /* ignore */ }
// Close all WebSocket connections
for (const ws of state.wsClients) {
try { ws.close(); } catch { /* ignore */ }
}
state.wsClients.clear();
// Resolve any pending polls with exit
for (const resolve of state.pendingPolls) {
resolve({ type: 'exit' });
}
state.pendingPolls.length = 0;
if (wss) { try { wss.close(); } catch { /* ignore */ } }
if (httpServer) { httpServer.close(); }
process.exit(0);
}
/**
* Start the live variant server.
* Called from liveCli() in detect-antipatterns.mjs.
*/
export async function startLiveServer({ port: requestedPort } = {}) {
const args = process.argv.slice(2);
const helpMode = args.includes('--help');
const stopMode = args.includes('stop');
const portArg = args.find(a => a.startsWith('--port='));
const parsedPort = portArg ? parseInt(portArg.split('=')[1], 10) : null;
if (helpMode) {
console.log(`Usage: impeccable live [options]
Start the live variant mode server. Serves the browser overlay script and
bridges WebSocket connections from the browser to the agent poll CLI.
Commands:
live Start the server (default)
live stop Stop a running live server
Options:
--port=PORT Use a specific port (default: auto-detect starting at 8400)
--help Show this help message
Endpoints:
/live.js Browser script for element picker + variant cycling
/detect.js Detection overlay script (backwards compatible)
/health Health check
/ws WebSocket endpoint for browser
/poll Long-poll endpoint for agent CLI`);
process.exit(0);
}
// Stop mode
if (stopMode) {
try {
const info = JSON.parse(fs.readFileSync(LIVE_PID_FILE, 'utf-8'));
const res = await fetch(`http://localhost:${info.port}/stop?token=${info.token}`);
if (res.ok) {
console.log(`Stopped live server on port ${info.port}.`);
}
} catch {
console.log('No running live server found.');
}
process.exit(0);
}
// Check for existing session
try {
const existing = JSON.parse(fs.readFileSync(LIVE_PID_FILE, 'utf-8'));
// Check if the process is actually running
try {
process.kill(existing.pid, 0);
console.error(`Live server already running on port ${existing.port} (pid ${existing.pid}).`);
console.error('Stop it first: npx impeccable live stop');
process.exit(1);
} catch {
// Process is dead, clean up stale PID file
fs.unlinkSync(LIVE_PID_FILE);
}
} catch {
// No PID file — good
}
// Generate session token
state.token = randomUUID();
state.port = requestedPort || parsedPort || await findOpenPort();
// Load scripts
const { detectScript, liveScript } = loadBrowserScripts();
// Inject token and port into the live browser script
const liveScriptWithToken =
`window.__IMPECCABLE_TOKEN__ = '${state.token}';\n` +
`window.__IMPECCABLE_PORT__ = ${state.port};\n` +
liveScript;
// Create HTTP server
httpServer = http.createServer(createRequestHandler({ detectScript, liveScriptWithToken }));
// Attach WebSocket
wss = setupWebSocket(httpServer);
// Start listening
httpServer.listen(state.port, '127.0.0.1', () => {
// Write PID file with token so poll CLI can authenticate
fs.writeFileSync(LIVE_PID_FILE, JSON.stringify({
pid: process.pid,
port: state.port,
token: state.token,
}));
const url = `http://localhost:${state.port}`;
console.log(`\nImpeccable live variant server running on ${url}`);
console.log(`Token: ${state.token}\n`);
console.log(`Inject into your page source:`);
console.log(` <script src="${url}/live.js"><\/script>\n`);
console.log(`Or inject via browser console:`);
console.log(` const s = document.createElement('script');`);
console.log(` s.src = '${url}/live.js';`);
console.log(` document.head.appendChild(s);\n`);
console.log(`Agent poll: npx impeccable poll`);
console.log(`Stop: npx impeccable live stop`);
});
process.on('SIGINT', shutdown);
process.on('SIGTERM', shutdown);
}