#!/usr/bin/env node import { readFile } from 'node:fs/promises'; const DEFAULT_PORT_FILE = '/Users/inmanx/Library/Application Support/Google/Chrome/DevToolsActivePort'; const EXTENSION_ID = 'kafggjjlhebccdgkechmbgflkaifkccf'; function argument(name, fallback = '') { const index = process.argv.indexOf(name); return index >= 0 && process.argv[index + 1] ? process.argv[index + 1] : fallback; } function connect(url) { return new Promise((resolve, reject) => { const socket = new WebSocket(url); const timer = setTimeout(() => reject(new Error('browser websocket connection timed out')), 15000); socket.addEventListener('open', () => { clearTimeout(timer); resolve(socket); }, { once: true }); socket.addEventListener('error', () => { clearTimeout(timer); reject(new Error('browser websocket connection failed')); }, { once: true }); }); } function messenger(socket) { let sequence = 0; const pending = new Map(); socket.addEventListener('message', (event) => { const message = JSON.parse(String(event.data || '{}')); const handler = pending.get(message.id); if (!handler) return; pending.delete(message.id); if (message.error) handler.reject(new Error(message.error.message || JSON.stringify(message.error))); else handler.resolve(message.result || {}); }); return (method, params = {}, sessionId = '', timeoutMs = 8000) => new Promise((resolve, reject) => { const id = ++sequence; const timer = setTimeout(() => { pending.delete(id); reject(new Error(`${method} timed out`)); }, timeoutMs); pending.set(id, { resolve: (value) => { clearTimeout(timer); resolve(value); }, reject: (error) => { clearTimeout(timer); reject(error); } }); socket.send(JSON.stringify({ id, method, params, ...(sessionId ? { sessionId } : {}) })); }); } const taskId = argument('--task'); const resumePrewrite = process.argv.includes('--resume-prewrite'); const reloadExtension = process.argv.includes('--reload-extension'); const cleanupRuntime = process.argv.includes('--cleanup-runtime'); const portFile = argument('--port-file', process.env.CDP_PORT_FILE || DEFAULT_PORT_FILE); const [port, browserId] = (await readFile(portFile, 'utf8')).trim().split(/\r?\n/); if (!port || !browserId) throw new Error(`invalid DevToolsActivePort file: ${portFile}`); const browserPath = browserId.startsWith('/') ? browserId : `/devtools/browser/${browserId}`; const socket = await connect(`ws://127.0.0.1:${port}${browserPath}`); const send = messenger(socket); try { const targets = await send('Target.getTargets'); const worker = (targets.targetInfos || []).find((target) => ( target.type === 'service_worker' && target.url === `chrome-extension://${EXTENSION_ID}/background.js` )); if (!worker) throw new Error(`extension service worker not found: ${EXTENSION_ID}`); const attached = await send('Target.attachToTarget', { targetId: worker.targetId, flatten: true }); const taskLiteral = JSON.stringify(taskId); const inspectionExpression = `Promise.all([ chrome.storage.local.get(null), Promise.resolve(typeof runningTasks === 'undefined' ? [] : [...runningTasks]) ]).then(([saved, running]) => { const taskId = ${taskLiteral}; const executions = saved.businessTaskExecutions || {}; const results = saved.businessTaskResults || {}; const result = results[taskId] || null; const task = saved.currentBusinessTask || null; const recent = Object.fromEntries(Object.entries(saved).filter(([key]) => /^last/i.test(key))); return { worker: { target_id: ${JSON.stringify(worker.targetId)}, url: ${JSON.stringify(worker.url)} }, running_tasks: running, execution: executions[taskId] || null, current_task: task ? { task_id: task.task_id, status: task.status, execution_id: task.execution_id, received_at: task.received_at, action: task.operation?.action || '' } : null, result: result ? { status: result.status, stage: result.stage, message: result.message, execution_id: result.execution_id, write_attempted: result.write_attempted, erp_write_started: result.erp_write_started, blockers: result.blockers || [], report_status: result.report?.status || '', erp_receipt: result.erp_receipt || result.report?.erp_receipt || null, verification: result.verification || result.report?.verification || null } : null, recent_storage: recent }; })`; const expression = cleanupRuntime ? 'cleanupStaleRuntimeState()' : reloadExtension ? `(() => { const version = chrome.runtime.getManifest().version; chrome.runtime.reload(); return { reload_requested: true, version }; })()` : resumePrewrite ? `resumePrewriteTask(${taskLiteral})` : inspectionExpression; const evaluated = await send('Runtime.evaluate', { expression, awaitPromise: true, returnByValue: true }, attached.sessionId, resumePrewrite ? 240_000 : 8_000); if (evaluated.exceptionDetails) throw new Error(evaluated.exceptionDetails.text || 'worker evaluation failed'); process.stdout.write(`${JSON.stringify(evaluated.result?.value || null, null, 2)}\n`); await send('Target.detachFromTarget', { sessionId: attached.sessionId }).catch(() => {}); } finally { socket.close(); }