/** Actual CLI -> API -> worker -> counterpart runs, then screenshots of their UI. */ import {chromium} from '@playwright/test'; import {execFileSync} from 'node:child_process'; import {createHash} from 'node:crypto'; import {mkdir,readFile,writeFile} from 'node:fs/promises'; import {fileURLToPath} from 'node:url'; import path from 'node:path'; const root=fileURLToPath(new URL('..',import.meta.url)); const out=path.join(root,'apps/web/public/showcase'); const evidence=path.join(root,'docs/evidence-showcase-cli'); const home=path.join(root,'.local/showcase-cli'); const base='http://127.0.0.1:8120'; const python=path.join(root,'.venv/Scripts/python.exe'); await mkdir(out,{recursive:true});await mkdir(evidence,{recursive:true}); function cli(args){return execFileSync(python,['-m','chameleon_cli','--home',home,...args],{cwd:root,encoding:'utf8',timeout:90000,windowsHide:true});} const cards=(await (await fetch(base+'/api/v1/examples')).json()).filter(e=>e.origin!=='synthetic'); const browser=await chromium.launch();const context=await browser.newContext({viewport:{width:1300,height:900}});const page=await context.newPage(); const errors=[];page.on('pageerror',e=>errors.push(e.message)); const examples=[];let transcript=''; try{ for(const [index,card] of cards.entries()){ let job; if(index===0){ transcript='$ chameleon point '+card.source+' --wait 45\n'+cli(['point',card.source,'--wait','45']); const state=JSON.parse(await readFile(path.join(home,createHash('sha256').update(base).digest('hex').slice(0,24)+'.json'),'utf8')); job=JSON.parse(cli(['--json','status',state.last_job])); await context.addCookies(state.cookies.map(c=>({...c,secure:false,httpOnly:c.name==='chameleon_session',sameSite:'Strict'}))); }else job=JSON.parse(cli(['--json','point',card.source,'--objective',card.objective,'--wait','45'])); if(job.status!=='VERIFIED'||job.counterpart?.status!=='READY')throw Error('Counterpart not ready: '+card.label); await page.goto(base);await page.getByText('YOUR PRIVATE GUEST SESSION').waitFor(); const response=await context.request.get(base+'/api/v1/twins/'+job.counterpart.twin_id);if(!response.ok())throw Error('Missing owned counterpart'); const twin=await response.json();const website=twin.manifest.kind==='local-website-reader-v1'; const region=page.getByRole('region',{name:website?'Website counterpart':'Local working twin'});await region.waitFor(); let checked; if(website){ const token=twin.pages[0].blocks.find(b=>b.text.length>30)?.text.split(/\s+/).find(w=>w.length>5); if(!token)throw Error('No searchable evidence'); await region.getByLabel('Search captured pages').fill(token); if(!await region.getByTestId('website-content').locator('p,h3,pre,blockquote').count())throw Error('Search returned no evidence'); await region.getByLabel('Search captured pages').fill(''); checked=['Captured content opens from saved state','Search returns matching source content','Observed links retained for bounded navigation']; }else{ const answer=JSON.parse(cli(['--json','query',twin.id,'count rows'])); const count=Object.values(twin.tables).reduce((n,t)=>n+t.rows.length,0); if(answer.result.answer!==count)throw Error('Copied-table count mismatch'); checked=['Live public CSV captured into an isolated table','Row count independently checked through the CLI','Local query, edit and export surface available']; } await region.scrollIntoViewIfNeeded(); const bounds=await region.boundingBox();await page.screenshot({path:path.join(out,'example-'+index+'.png'),clip:{x:bounds.x,y:bounds.y,width:bounds.width,height:Math.min(bounds.height,740)}}); examples.push({source:card.source,label:card.label,image:'showcase/example-'+index+'.png',kind:website?'Website reader':'Editable data copy',provider:job.form.provider,captured_at:job.evidence.captured_at,evidence_digest:job.evidence.content_digest,checked,pages:twin.pages.length,rows:Object.values(twin.tables).reduce((n,t)=>n+t.rows.length,0)}); console.log(JSON.stringify({example:card.label,status:'VERIFIED',provider:job.form.provider})); } await writeFile(path.join(out,'cli-transcript.txt'),transcript); const terminal=await context.newPage(); const escape=s=>s.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); await terminal.setViewportSize({width:1100,height:890}); await terminal.setContent('
■ ■ ■  CHAMELEON / LOCAL TERMINAL
'+escape(transcript)+'
ACTUAL CLI OUTPUT / CHAMELEON PROVIDER / NO SOURCE WRITES
'); await terminal.locator('.frame').screenshot({path:path.join(out,'cli.png')}); await terminal.close(); if(errors.length)throw Error('Browser errors: '+errors.join(', ')); const manifest={captured_at:new Date().toISOString(),examples,cli:{image:'showcase/cli.png',transcript:'showcase/cli-transcript.txt',provider:'chameleon-local-v1'}}; await writeFile(path.join(out,'manifest.json'),JSON.stringify(manifest,null,2)); await writeFile(path.join(evidence,'capture-results.json'),JSON.stringify({recorded_at:manifest.captured_at,actual_sources:examples.length,provider:'chameleon-local-v1',entry:'installed CLI harness',browser_errors:errors,public_deployment:false},null,2)); }finally{await browser.close();}