diff --git a/.agents/skills/impeccable/scripts/live-browser.js b/.agents/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.agents/skills/impeccable/scripts/live-browser.js +++ b/.agents/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.claude/skills/impeccable/scripts/live-browser.js b/.claude/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.claude/skills/impeccable/scripts/live-browser.js +++ b/.claude/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.cursor/skills/impeccable/scripts/live-browser.js b/.cursor/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.cursor/skills/impeccable/scripts/live-browser.js +++ b/.cursor/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.gemini/skills/impeccable/scripts/live-browser.js b/.gemini/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.gemini/skills/impeccable/scripts/live-browser.js +++ b/.gemini/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.github/skills/impeccable/scripts/live-browser.js b/.github/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.github/skills/impeccable/scripts/live-browser.js +++ b/.github/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.kiro/skills/impeccable/scripts/live-browser.js b/.kiro/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.kiro/skills/impeccable/scripts/live-browser.js +++ b/.kiro/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.opencode/skills/impeccable/scripts/live-browser.js b/.opencode/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.opencode/skills/impeccable/scripts/live-browser.js +++ b/.opencode/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.pi/skills/impeccable/scripts/live-browser.js b/.pi/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.pi/skills/impeccable/scripts/live-browser.js +++ b/.pi/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.rovodev/skills/impeccable/scripts/live-browser.js b/.rovodev/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.rovodev/skills/impeccable/scripts/live-browser.js +++ b/.rovodev/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.trae-cn/skills/impeccable/scripts/live-browser.js b/.trae-cn/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.trae-cn/skills/impeccable/scripts/live-browser.js +++ b/.trae-cn/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/.trae/skills/impeccable/scripts/live-browser.js b/.trae/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/.trae/skills/impeccable/scripts/live-browser.js +++ b/.trae/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/source/skills/impeccable/scripts/live-browser.js b/source/skills/impeccable/scripts/live-browser.js index 82afa6b61..66a54435a 100644 --- a/source/skills/impeccable/scripts/live-browser.js +++ b/source/skills/impeccable/scripts/live-browser.js @@ -905,7 +905,12 @@ pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); }); row.appendChild(pill); - // Freeform input + // Freeform input. Focus state shows an accent-colored border only — + // an earlier version tinted the background with `BP.accentSoft`, which + // composited against the dark bar surface to a murky purple where the + // browser's default placeholder gray was unreadable. Placeholder color + // is set explicitly via a one-shot stylesheet keyed off this input's id + // so it picks up the bar's `textDim` token in both themes. const input = document.createElement('input'); input.id = PREFIX + '-input'; input.type = 'text'; @@ -916,15 +921,20 @@ border: '1px solid transparent', background: 'transparent', fontFamily: FONT, fontSize: '12px', color: BP.text, outline: 'none', - transition: 'border-color 0.15s ease, background 0.15s ease', + transition: 'border-color 0.15s ease', }); + if (!document.getElementById(PREFIX + '-input-style')) { + const s = document.createElement('style'); + s.id = PREFIX + '-input-style'; + s.textContent = + '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }'; + document.head.appendChild(s); + } input.addEventListener('focus', () => { - input.style.borderColor = BP.hairline; - input.style.background = BP.accentSoft; + input.style.borderColor = BP.accent; }); input.addEventListener('blur', () => { input.style.borderColor = 'transparent'; - input.style.background = 'transparent'; }); input.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; } diff --git a/tests/live-e2e/agent.mjs b/tests/live-e2e/agent.mjs index c33f012e4..ff00542f7 100644 --- a/tests/live-e2e/agent.mjs +++ b/tests/live-e2e/agent.mjs @@ -318,12 +318,17 @@ export async function runAgentLoop({ log(`generate id=${event.id} action=${event.action} count=${event.count}`); try { // 1. Wrap the original element in the variant scaffold (deterministic CLI) + // wrapTarget can be a static {classes, tag, elementId} (test fixtures + // know what they pick) or a function (event) => target (real-use + // sessions: the agent must derive the selector from the picked + // element on the fly). + const target = typeof wrapTarget === 'function' ? wrapTarget(event) : wrapTarget; const wrapInfo = await runWrap({ tmp, scriptsDir, id: event.id, count: event.count, - ...wrapTarget, + ...target, }); log(`wrapped: ${wrapInfo.file} insertLine=${wrapInfo.insertLine}`); diff --git a/tools/live-loop.mjs b/tools/live-loop.mjs new file mode 100644 index 000000000..9ca4419fe --- /dev/null +++ b/tools/live-loop.mjs @@ -0,0 +1,85 @@ +/** + * Attach an LLM-backed agent loop to a live-server that is already running + * in the current working directory. Reads port + token from the PID file + * (.impeccable-live.json) that live-server.mjs --background writes on boot. + * + * Usage (from the project root): + * ANTHROPIC_API_KEY=... node tools/live-loop.mjs + * + * Optional: + * IMPECCABLE_E2E_LLM_MODEL=claude-sonnet-4-6 (default: claude-haiku-4-5) + * + * Stop with Ctrl-C; the live-server keeps running until you call + * `node source/skills/impeccable/scripts/live-server.mjs stop`. + */ + +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { runAgentLoop } from '../tests/live-e2e/agent.mjs'; +import { createLlmAgent } from '../tests/live-e2e/agents/llm-agent.mjs'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.join(__dirname, '..'); +const SCRIPTS_DIR = path.join(REPO_ROOT, 'source', 'skills', 'impeccable', 'scripts'); +const PID_FILE = path.join(REPO_ROOT, '.impeccable-live.json'); + +if (!fs.existsSync(PID_FILE)) { + console.error( + `No live-server PID file at ${PID_FILE}. Start one first:\n` + + ` node source/skills/impeccable/scripts/live-server.mjs --background\n` + + ` node source/skills/impeccable/scripts/live-inject.mjs --port `, + ); + process.exit(1); +} + +const { port, token } = JSON.parse(fs.readFileSync(PID_FILE, 'utf-8')); +console.log(`Attaching agent loop to live-server on :${port}`); + +const agent = await createLlmAgent({ + model: process.env.IMPECCABLE_E2E_LLM_MODEL, + log: (m) => console.log('[llm] ' + m), +}); +if (!agent) { + console.error('ANTHROPIC_API_KEY is not set. Set it and re-run.'); + process.exit(1); +} + +const controller = new AbortController(); + +process.on('SIGINT', () => { + console.log('\nStopping agent loop (live-server stays running).'); + controller.abort(); + setTimeout(() => process.exit(0), 200); +}); +process.on('SIGTERM', () => controller.abort()); + +console.log( + `Agent ready (model=${process.env.IMPECCABLE_E2E_LLM_MODEL || 'claude-haiku-4-5'}).\n` + + `Pick an element in the browser and hit Go. Ctrl-C to stop.`, +); + +await runAgentLoop({ + tmp: REPO_ROOT, + scriptsDir: SCRIPTS_DIR, + port, + token, + agent, + signal: controller.signal, + log: (m) => console.log('[agent] ' + m), + // Per-event wrap target derived from the picked element's payload. + // Preference: id > first class > tag-only. + wrapTarget: (event) => { + const el = event.element || {}; + const out = {}; + if (el.id) out.elementId = el.id; + else if (Array.isArray(el.classes) && el.classes.length > 0) { + // live-wrap matches when ALL listed classes are present on the source + // node, so include only the first to maximize match likelihood. + out.classes = el.classes[0]; + } + if (el.tagName) out.tag = el.tagName.toLowerCase(); + return out; + }, +});