// Invoke the public repaired REA CLI. This wrapper has no target protocol access. import fs from 'node:fs/promises' import path from 'node:path' import { spawn } from 'node:child_process' import { fileURLToPath } from 'node:url' const project = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..') process.chdir(project) const includeVisuals = process.argv.includes('--visuals') const args = process.argv.slice(2).filter(arg => arg !== '--visuals') function option(name) { const index=args.indexOf(name);if(index<0)return undefined;const value=args[index+1];if(!value||value.startsWith('--'))throw new Error(`Missing ${name} value`);args.splice(index,2);return value } const probeFile=option('--probe'),outputDirectory=option('--output'),selectedTargetId=option('--target-id') const probe=probeFile?JSON.parse(await fs.readFile(probeFile,'utf8')):undefined if(probe&&!includeVisuals)throw new Error('--probe requires --visuals') const endpoint = args[0] || 'http://127.0.0.1:9223' const expectedPage = args[1] || 'C:/Users/Administrator/AppData/Local/Programs/WorkBuddy/resources/app.asar/renderer/index.html' const launcher = path.resolve('.tmp/rea-css-runtime/scripts/rea.mjs') await fs.access(launcher).catch(() => { throw new Error('Run npm run rea:css:prepare first') }) async function run(args) { return new Promise((resolve, reject) => { const child = spawn(process.execPath, [launcher, ...args, '--json'], { cwd: project, stdio: ['ignore', 'pipe', 'inherit'], windowsHide: true }) const chunks = [] child.stdout.on('data', chunk => chunks.push(chunk)) child.once('error', reject) child.once('close', async code => { if (code === 0) return resolve(Buffer.concat(chunks)) await fs.writeFile('.tmp/rea-capture-failure.json', Buffer.concat(chunks)) reject(new Error(`REA exited with ${code}; diagnostic saved to .tmp/rea-capture-failure.json`)) }) }) } const normalize = value => value.replace(/\\/g, '/').toLowerCase() const discoveryBytes = await run(['list-electron-targets', endpoint]) const discovery = JSON.parse(discoveryBytes.toString('utf8')) if (discovery.operation !== 'list_electron_targets') throw new Error('REA did not return target discovery Evidence') const matches = discovery.normalized_result.targets.filter(target => normalize(target.file_path) === normalize(expectedPage) && (!selectedTargetId || target.target_id === selectedTargetId)) if (matches.length !== 1) throw new Error(`Expected exactly one authorized WorkBuddy page; found ${matches.length}`) const bytes = await run(['inspect-electron-page', endpoint, matches[0].target_id, '--include-style-sources', ...(includeVisuals ? ['--include-visuals'] : []), ...(probe?['--visual-probe',JSON.stringify(probe)]:[]), '--observation-ms', '250']) const evidence = JSON.parse(bytes.toString('utf8')) if (evidence.operation !== 'inspect_electron_page' || evidence.parameters?.include_style_sources !== true || !evidence.normalized_result?.styles) throw new Error('REA did not return CSS Evidence') await fs.writeFile('.tmp/rea-css-targets.json', discoveryBytes) await fs.writeFile('.tmp/rea-workbuddy-css-evidence.json', bytes) await fs.mkdir('.tmp/rea-css-evidence', { recursive: true }) if (!/^ev_[a-f0-9]{64}$/.test(evidence.evidence_id) || !/^ev_[a-f0-9]{64}$/.test(discovery.evidence_id)) throw new Error('Invalid REA Evidence identity') await fs.writeFile(path.join('.tmp/rea-css-evidence', `${discovery.evidence_id}.json`), discoveryBytes) const archive = path.join('.tmp/rea-css-evidence', `${evidence.evidence_id}.json`) await fs.writeFile(archive, bytes) if (includeVisuals) { const visuals = evidence.normalized_result.visuals if (!visuals) throw new Error('REA did not retain visual capture') const visualDirectory=outputDirectory?path.resolve(outputDirectory):path.resolve('.tmp/rea-visual-reference') await fs.mkdir(visualDirectory, { recursive: true }) const { createHash } = await import('node:crypto') const image = Buffer.from(visuals.artifact.data_base64, 'base64') if (createHash('sha256').update(image).digest('hex') !== visuals.artifact.sha256 || image.length !== visuals.artifact.bytes) throw new Error('REA screenshot digest mismatch') await fs.writeFile(path.join(visualDirectory,'workbuddy.png'), image) await fs.writeFile(path.join(visualDirectory,'layout.json'), JSON.stringify({ evidenceId: evidence.evidence_id, archive: path.resolve(archive), ...visuals, artifact: { ...visuals.artifact, data_base64: undefined },probe:visuals.probe?{...visuals.probe,frames:visuals.probe.frames.map(f=>({...f,artifact:{...f.artifact,data_base64:undefined}}))}:undefined }, null, 2)) for(let index=0;index<(visuals.probe?.frames.length??0);index++){ const frame=visuals.probe.frames[index],png=Buffer.from(frame.artifact.data_base64,'base64'); if(createHash('sha256').update(png).digest('hex')!==frame.artifact.sha256)throw new Error('REA scenario frame digest mismatch'); await fs.writeFile(path.join(visualDirectory,`frame-${String(index).padStart(3,'0')}.png`),png) } if(visuals.font_samples){ const os=await import('node:os'); const report={evidenceId:evidence.evidence_id,archive:path.resolve(archive),evidenceSha256:createHash('sha256').update(bytes).digest('hex'),version:evidence.normalized_result.browser.user_agent.match(/WorkBuddy[ /]([\d.]+)/i)?.[1]??'unobserved',environment:{platform:os.platform(),osRelease:os.release(),browser:evidence.normalized_result.browser},source:{kind:'rea-workbuddy'},samples:visuals.font_samples,missing:visuals.font_missing??[]}; await fs.writeFile(path.join(visualDirectory,'font-platform-report.json'),JSON.stringify(report,null,2)) await fs.writeFile('.tmp/rea-font-platform-report.json',JSON.stringify(report,null,2)) } } const styles = evidence.normalized_result.styles console.log(JSON.stringify({ evidenceId: evidence.evidence_id, targetId: matches[0].target_id, total: styles.total, included: styles.items.filter(item => item.source.included).length, excluded: styles.excluded, sourceFailures: styles.source_failures, truncated: styles.truncated, output: path.resolve('.tmp/rea-workbuddy-css-evidence.json'), archive: path.resolve(archive) }, null, 2))