prompttest-mobile 1.5.1 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/prompttest.js +1 -1
- package/dist/engine.bundle.js +142 -142
- package/dist/index.js +142 -142
- package/dist/lib/live-server.d.ts +4 -0
- package/package.json +1 -1
package/dist/bin/prompttest.js
CHANGED
|
@@ -898,7 +898,7 @@ ${c.errors.join(`
|
|
|
898
898
|
</hierarchy>`;if(this.isMock||process.platform!=="darwin")return e;if(this.wdaBaseUrl)try{let s=await fetch(`${this.wdaBaseUrl}/source?format=xml`);if(s.ok)return await s.text()}catch{}return e}async tap(t,e,s){let n=await this.ensureDefaultDevice(s);if(!(this.isMock||process.platform!=="darwin")){if(this.wdaBaseUrl){await fetch(`${this.wdaBaseUrl}/wda/tap/nil`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({x:t,y:e})}).catch(()=>{});return}await new Promise(i=>{Wt("idb",["ui","tap",`${t}`,`${e}`,"--udid",n],()=>i())})}}async inputText(t,e){let s=await this.ensureDefaultDevice(e);if(!(this.isMock||process.platform!=="darwin")){if(this.wdaBaseUrl){await fetch(`${this.wdaBaseUrl}/wda/keys`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:t.split("")})}).catch(()=>{});return}await new Promise(n=>{Wt("idb",["ui","type",t,"--udid",s],()=>n())})}}async pressKey(t,e){this.isMock||process.platform!=="darwin"||(await this.ensureDefaultDevice(e),this.wdaBaseUrl&&await fetch(`${this.wdaBaseUrl}/wda/keys`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({value:[t]})}).catch(()=>{}))}async swipe(t,e,s,n,i=300,r){this.isMock||process.platform!=="darwin"||(await this.ensureDefaultDevice(r),this.wdaBaseUrl&&await fetch(`${this.wdaBaseUrl}/wda/dragfromtoforduration`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({fromX:t,fromY:e,toX:s,toY:n,duration:i/1e3})}).catch(()=>{}))}async launchApp(t,e){let s=await this.ensureDefaultDevice(e);if(!(this.isMock||process.platform!=="darwin"))return new Promise(n=>{Wt("xcrun",["simctl","launch",s,t],()=>n())})}async forceStop(t,e){let s=await this.ensureDefaultDevice(e);if(!(this.isMock||process.platform!=="darwin"))return new Promise(n=>{Wt("xcrun",["simctl","terminate",s,t],()=>n())})}async isAppInstalled(t,e){let s=await this.ensureDefaultDevice(e);return this.isMock||process.platform!=="darwin"?!0:new Promise(n=>{Wt("xcrun",["simctl","get_app_container",s,t],i=>{n(!i)})})}async getForegroundPackage(t){return"com.apple.mobilesafari"}}});var li={};Qe(li,{startLiveServer:()=>Zn});import $o from"http";import te from"fs";import ee from"path";import ci from"os";import{fileURLToPath as Co}from"url";async function Zn(a={}){let t=a.port??4040,e=a.host||"127.0.0.1",s=process.cwd();(s==="/"||s==="\\")&&(s=ci.homedir?ci.homedir():process.env.HOME||process.env.USERPROFILE||"/tmp");let n=a.outputDir?ee.resolve(a.outputDir):ee.resolve(s,"output"),i=a.suiteName||"PromptTest Live Monitor",r=a.initialReport||{packageName:"PromptTest Execution",deviceId:"Live Device",startTime:new Date().toISOString(),endTime:new Date().toISOString(),durationSeconds:0,totalSteps:0,passedSteps:0,failedSteps:0,skippedSteps:0,warnSteps:0,errorsDetected:[],steps:[]},o=new Set,l=setInterval(()=>{for(let x of o)try{x.write(`: heartbeat
|
|
899
899
|
|
|
900
900
|
`)}catch{o.delete(x)}},15e3),c=a.driver||new lt,d=ee.dirname(Co(import.meta.url)),p=a.specsDir?ee.resolve(a.specsDir):ee.resolve(process.cwd(),"specs");if(!te.existsSync(p)){let x=ee.resolve(d,"../specs");te.existsSync(x)&&(p=x)}let u=null;function h(){return u||(u=new st(c)),u}let m=ee.join(n,"baselines"),b=new Ut(m),T=null,w=null,S=["strict","moderate","interactive","disabled"],y=null,P=0;function N(x){return x?{success:!0,sessionId:x.id,status:x.status,packageName:x.packageName,deviceId:x.deviceId,safetyMode:x.safetyMode,maxDepth:x.maxDepth,maxSteps:x.maxSteps,maxScreens:x.maxScreens,screensDiscovered:x.screensDiscovered,elementsMapped:x.elementsMapped,stepsTaken:x.stepsTaken,startedAt:x.startedAt,finishedAt:x.finishedAt,crashes:x.crashes,screens:x.screens,blocked:x.blocked,events:x.events,specLines:x.specLines,error:x.error}:{success:!0,sessionId:null,status:"idle",packageName:"",safetyMode:"strict",screensDiscovered:0,elementsMapped:0,stepsTaken:0,maxDepth:0,maxSteps:0,crashes:[],screens:[],blocked:[],events:[],specLines:[]}}function O(x){let f=x.match(/"([^"]+)"/);return f?f[1]:null}function L(x){let f=x.specLines.length>0?x.specLines:["Launch the app"],F=["# Auto-generated by PromptTest Auto-Pilot (Autonomous Explorer)",`# Package: ${x.packageName}`,`# Safety Policy: ${x.safetyMode.toUpperCase()} | Max Depth: ${x.maxDepth} | Step Budget: ${x.maxSteps}`,`# Explored at: ${x.startedAt}`,`# Screens Discovered: ${x.screensDiscovered} | Steps Executed: ${x.stepsTaken}`];return x.deviceId&&F.push(`# Device: ${x.deviceId}`),[...F,"",...f,""].join(`
|
|
901
|
-
`)}function U(x,f,F){x.status=f,x.finishedAt=new Date().toISOString(),F&&(x.error=F),K("explore",{...N(x),event:{type:f==="failed"?"error":"done",description:F||f}})}function j(x){let f=y;if(f){if(typeof x.totalSteps=="number"&&(f.stepsTaken=Math.max(f.stepsTaken,x.totalSteps)),typeof x.discoveredScreens=="number"&&(f.screensDiscovered=Math.max(f.screensDiscovered,x.discoveredScreens)),x.type==="action"){let F=O(x.description);F&&f.elementLabels.add(F),x.description&&f.specLines[f.specLines.length-1]!==x.description&&f.specLines.push(x.description)}if(x.screenHash){let F=x.screenTitle||x.description||x.screenHash.slice(0,8),E=f.screens.find(ge=>ge.hash===x.screenHash);E?(E.visits++,typeof x.elementCount=="number"&&(E.elements=x.elementCount),E.label===E.hash.slice(0,8)&&x.screenTitle&&(E.label=x.screenTitle,E.title=x.screenTitle)):f.screens.push({id:x.screenHash,hash:x.screenHash,depth:x.depth??f.screens.length,title:F,label:F,visits:1,elements:x.elementCount??0}),f.screensDiscovered=Math.max(f.screensDiscovered,f.screens.length)}if(x.type==="crash"){let F=new Date().toISOString(),E=x.screenHash,ge=E?f.screens.find(Y=>Y.hash===E):void 0;f.crashes.push({id:E||`crash-${f.crashes.length+1}`,type:x.description.toLowerCase().includes("anr")?"anr":"crash",message:x.description,screen:ge?.label||E?.slice(0,8),timestamp:F,description:x.description,screenHash:E})}x.type==="safety_block"&&f.blocked.push(x.description),f.elementsMapped=f.elementLabels.size,f.events.push(x),f.events.length>300&&f.events.shift(),K("explore",{...N(f),event:x})}}function Q(x){let f=S.includes(x.safetyMode||"")?x.safetyMode:"strict";P++;let F={id:`explore-${Date.now()}-${P}`,packageName:x.packageName,deviceId:x.deviceId,safetyMode:f,maxDepth:x.maxDepth??5,maxSteps:x.maxSteps??100,maxScreens:x.maxScreens??30,status:"running",startedAt:new Date().toISOString(),screensDiscovered:0,elementsMapped:0,stepsTaken:0,screens:[],elementLabels:new Set,crashes:[],blocked:[],events:[],specLines:[],abort:{aborted:!1}};y=F;let E=new Ht(c);return K("explore",{...N(F),event:{type:"state_change",description:`Auto-Pilot launched on ${F.packageName} (${F.safetyMode} mode)`}}),E.explore({packageName:F.packageName,serial:F.deviceId,strategy:"dfs",safetyMode:F.safetyMode,maxDepth:F.maxDepth,maxScreens:F.maxScreens,stepBudget:F.maxSteps,screenshots:"state-change",onStep:j,abortSignal:F.abort}).then(()=>{U(F,F.abort.aborted?"stopped":"completed")}).catch(ge=>{U(F,F.abort.aborted?"stopped":"failed",ge instanceof Error?ge.message:String(ge))}),F}function K(x,f){let F=`event: ${x}
|
|
901
|
+
`)}function U(x,f,F){x.status=f,x.finishedAt=new Date().toISOString(),F&&(x.error=F),K("explore",{...N(x),event:{type:f==="failed"?"error":"done",description:F||f}})}function j(x){let f=y;if(f){if(typeof x.totalSteps=="number"&&(f.stepsTaken=Math.max(f.stepsTaken,x.totalSteps)),typeof x.discoveredScreens=="number"&&(f.screensDiscovered=Math.max(f.screensDiscovered,x.discoveredScreens)),x.type==="action"){let F=O(x.description);F&&f.elementLabels.add(F),x.description&&f.specLines[f.specLines.length-1]!==x.description&&f.specLines.push(x.description)}if(x.screenHash){let F=x.screenTitle||x.description||x.screenHash.slice(0,8),E=f.screens.find(ge=>ge.hash===x.screenHash);E?(E.visits++,typeof x.elementCount=="number"&&(E.elements=x.elementCount),E.label===E.hash.slice(0,8)&&x.screenTitle&&(E.label=x.screenTitle,E.title=x.screenTitle)):f.screens.push({id:x.screenHash,hash:x.screenHash,depth:x.depth??f.screens.length,title:F,label:F,visits:1,elements:x.elementCount??0}),f.screensDiscovered=Math.max(f.screensDiscovered,f.screens.length)}if(x.type==="crash"){let F=new Date().toISOString(),E=x.screenHash,ge=E?f.screens.find(Y=>Y.hash===E):void 0;f.crashes.push({id:E||`crash-${f.crashes.length+1}`,type:x.description.toLowerCase().includes("anr")?"anr":"crash",message:x.description,screen:ge?.label||E?.slice(0,8),timestamp:F,description:x.description,screenHash:E})}x.type==="safety_block"&&f.blocked.push(x.description),f.elementsMapped=f.elementLabels.size,f.events.push(x),f.events.length>300&&f.events.shift(),K("explore",{...N(f),event:x})}}function Q(x){let f=S.includes(x.safetyMode||"")?x.safetyMode:"strict";P++;let F={id:`explore-${Date.now()}-${P}`,packageName:x.packageName,deviceId:x.deviceId,safetyMode:f,maxDepth:x.maxDepth??5,maxSteps:x.maxSteps??100,maxScreens:x.maxScreens??30,status:"running",startedAt:new Date().toISOString(),screensDiscovered:0,elementsMapped:0,stepsTaken:0,screens:[],elementLabels:new Set,crashes:[],blocked:[],events:[],specLines:[],abort:{aborted:!1}};y=F;let E=a.explorerFactory?a.explorerFactory(c):new Ht(c);return K("explore",{...N(F),event:{type:"state_change",description:`Auto-Pilot launched on ${F.packageName} (${F.safetyMode} mode)`}}),E.explore({packageName:F.packageName,serial:F.deviceId,strategy:"dfs",safetyMode:F.safetyMode,maxDepth:F.maxDepth,maxScreens:F.maxScreens,stepBudget:F.maxSteps,screenshots:"state-change",onStep:j,abortSignal:F.abort}).then(()=>{U(F,F.abort.aborted?"stopped":"completed")}).catch(ge=>{U(F,F.abort.aborted?"stopped":"failed",ge instanceof Error?ge.message:String(ge))}),F}function K(x,f){let F=`event: ${x}
|
|
902
902
|
data: ${JSON.stringify(f)}
|
|
903
903
|
|
|
904
904
|
`;for(let E of o)try{E.write(F)}catch{o.delete(E)}}function V(x){return new Promise((f,F)=>{let E="";x.on("data",ge=>{E+=ge}),x.on("end",()=>{try{f(E?JSON.parse(E):{})}catch{F(new Error("Invalid JSON payload"))}}),x.on("error",F)})}let ye=$o.createServer(async(x,f)=>{if(f.setHeader("Access-Control-Allow-Origin","*"),f.setHeader("Access-Control-Allow-Methods","GET, POST, OPTIONS"),f.setHeader("Access-Control-Allow-Headers","Content-Type, Authorization"),x.method==="OPTIONS"){f.writeHead(204),f.end();return}let F=new URL(x.url||"/",`http://${e}:${t}`),E=decodeURIComponent(F.pathname);if(E==="/api/devices"){try{let[g,k]=await Promise.all([c.getRawConnectedDevices().catch(()=>[]),new Rs().getConnectedDevices().catch(()=>[])]),M=c.deduplicateDevices(g).map(A=>({...A,platform:"android"})),v=(k||[]).map(A=>({serial:A.serial,state:A.state,model:A.model||"iOS Device",product:"iOS",connectionType:A.state==="simulator"?"emulator":"usb",platform:"ios"})),C=[...M,...v];f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,devices:C,allEndpoints:g}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/screen-state"){try{let g=F.searchParams.get("deviceId")||void 0,k=F.searchParams.get("hierarchy")!=="false",[M,v,C]=await Promise.all([c.captureScreenshot(g),k?c.getUiHierarchy(g):Promise.resolve(null),c.getForegroundPackage(g)]),A=[];if(v){let{flat:D}=be(v);A=D.map(I=>({index:I.index,text:I.text,resourceId:I.resourceId,className:I.className,contentDesc:I.contentDesc,clickable:I.clickable,scrollable:I.scrollable,bounds:I.bounds}))}f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,deviceId:g||c.getDefaultDevice()||"default",foregroundApp:C,screenshot:`data:image/png;base64,${M.toString("base64")}`,elements:k?A:void 0,elementCount:A.length}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/tap"&&x.method==="POST"){try{let g=await V(x);if(typeof g.x!="number"||typeof g.y!="number"){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"Missing numeric coordinates: x and y"}));return}await c.tap(g.x,g.y,g.deviceId),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,tapped:{x:g.x,y:g.y}}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/type"&&x.method==="POST"){try{let g=await V(x);if(typeof g.text!="string"){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"Missing text string to type"}));return}await c.inputText(g.text,g.deviceId),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,typed:g.text}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/key"&&x.method==="POST"){try{let g=await V(x);if(g.keyCode===void 0){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"Missing keyCode parameter"}));return}await c.pressKey(g.keyCode,g.deviceId),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,keyCode:g.keyCode}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/wake"&&x.method==="POST"){try{let g=await V(x);await c.wakeDevice(g.deviceId),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/wifi/connect"&&x.method==="POST"){try{let g=await V(x),k=await c.connectWifi(g.target,g.port||5555,g.deviceId);f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:k.success,target:k.target,message:k.message}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/wifi/disconnect"&&x.method==="POST"){try{let g=await V(x),k=await c.disconnectWifi(g.target);f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,message:k}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/wifi/pair"&&x.method==="POST"){try{let g=await V(x);if(!g.target||!g.pairingCode){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"Missing target or pairingCode"}));return}let k=await c.pairWifi(g.target,g.pairingCode);f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify(k))}catch(g){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/devices/install-apk"&&x.method==="POST"){try{let g=F.searchParams,k=g.get("deviceId")||void 0,M=g.get("targetDevices"),v=M?M.split(",").filter(Boolean):k?[k]:[],C=g.get("fileName")||`app_${Date.now()}.apk`,A=ee.basename(C).endsWith(".apk")?ee.basename(C):`${ee.basename(C)}.apk`,D=ee.join(n,"_apk_temp");te.existsSync(D)||te.mkdirSync(D,{recursive:!0});let I=ee.join(D,`${Date.now()}_${A}`),H=te.createWriteStream(I);await new Promise((q,Se)=>{x.pipe(H),x.on("error",Se),H.on("error",Se),H.on("finish",q)});let z=v;if(z.length===0){let q=c.getDefaultDevice();q&&(z=[q])}if(z.length===0&&(z=(await c.getConnectedDevices()).filter(Se=>Se.state==="device").map(Se=>Se.serial)),z.length===0){try{await te.promises.unlink(I)}catch{}f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"No active target device found to install APK"}));return}let G=await Promise.allSettled(z.map(q=>c.installApk(I,q)));try{await te.promises.unlink(I)}catch{}let Z=G.filter(q=>q.status==="fulfilled").length,X=[];G.forEach((q,Se)=>{if(q.status==="rejected"){let Pe=q.reason instanceof Error?q.reason.message:String(q.reason);X.push(`${z[Se]}: ${Pe}`)}}),Z>0?(K("apk-installed",{fileName:A,installedCount:Z,targets:z}),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,message:`Successfully installed "${A}" on ${Z} device(s)${X.length>0?` (Failed: ${X.join(", ")})`:""}`,installedCount:Z}))):(f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:`Failed to install APK: ${X.join("; ")}`})))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/metrics"&&x.method==="GET"){try{let g=F.searchParams.get("deviceId")||void 0,k=await c.getDeviceMetrics(g);if(Object.keys(k).length===0)throw new Error(`No metrics available for device ${g||"default"}. Confirm the device is connected and authorized.`);f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,metrics:k}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/doctor"&&x.method==="GET"){try{let g=F.searchParams.get("deviceId")||void 0,k=F.searchParams.get("packageName")||void 0,M=await gs(c,{serial:g,packageName:k});f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,report:M}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if((E==="/api/explore/start"||E==="/api/explore")&&x.method==="POST"){try{let g=await V(x),k=(g.packageName||g.targetPackage||"").trim();if(!k){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"Missing packageName (packageName or targetPackage)"}));return}if(y&&y.status==="running"){f.writeHead(409,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"An Auto-Pilot exploration session is already active",sessionId:y.id}));return}let M=Q({packageName:k,deviceId:g.deviceId||g.serial,safetyMode:g.safetyMode||g.safety,maxDepth:g.maxDepth??g.depth,maxSteps:g.maxSteps??g.stepBudget,maxScreens:g.maxScreens});f.writeHead(202,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,sessionId:M.id,status:M.status,packageName:M.packageName}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if((E==="/api/explore/status"||E==="/api/explore")&&x.method==="GET"){f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify(N(y)));return}if((E==="/api/explore/stop"||E==="/api/explore")&&(x.method==="POST"||x.method==="DELETE")){if(!y){f.writeHead(404,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"No active Auto-Pilot session to stop"}));return}y.abort.aborted=!0,f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,sessionId:y.id,status:"stopping",packageName:y.packageName}));return}if(E==="/api/explore/export"&&x.method==="POST"){try{let g=await V(x);if(!y){f.writeHead(404,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"No exploration session available to export"}));return}let k=(g.specName||`explored_${y.packageName}.txt`).trim(),M=ee.basename(k).replace(/[^a-zA-Z0-9._-]/g,"_"),v=M.endsWith(".txt")?M:`${M}.txt`,C=L(y);await te.promises.mkdir(p,{recursive:!0}),await te.promises.writeFile(ee.join(p,v),C,"utf8"),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,specName:v,content:C,screensDiscovered:y.screensDiscovered,stepsTaken:y.stepsTaken}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/record/start"&&x.method==="POST"){try{let g=await V(x);if(T){f.writeHead(409,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"A recording session is already active"}));return}let k=g.deviceId||c.getDefaultDevice()||void 0,M=g.specName?ee.join(p,ee.basename(g.specName)):void 0;T=new At(c),w=k||"default",T.onStep=v=>{K("record-step",{step:v,deviceId:k})},await T.startSession(M,k,{append:g.append}),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,message:"Recording session started",deviceId:k}))}catch(g){if(T)try{await T.stopSession()}catch{}T=null,w=null,f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/record/stop"&&x.method==="POST"){try{if(!T){f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,message:"No active recording session",steps:[]}));return}let g=await T.stopSession();T=null,w=null,f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,steps:g.steps,specPath:g.specPath,reportPath:g.reportPath}))}catch(g){T=null,w=null,f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/record/status"&&x.method==="GET"){f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,isRecording:T!==null,deviceId:w}));return}if(E==="/api/specs"&&x.method==="GET"){try{te.existsSync(p)||te.mkdirSync(p,{recursive:!0});let k=te.readdirSync(p).filter(M=>M.endsWith(".txt")||M.endsWith(".spec")).map(M=>{let v=ee.join(p,M),C=te.statSync(v);return{name:M,path:ee.relative(process.cwd(),v).replace(/\\/g,"/"),size:C.size,modified:C.mtime.toISOString()}});f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,specs:k}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if((E.startsWith("/api/specs/")||E==="/api/spec")&&x.method==="GET"){try{let g="";E.startsWith("/api/specs/")?g=decodeURIComponent(E.slice(11)):g=F.searchParams.get("name")||"";let k=ee.basename(g.trim());if(!k){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"Missing spec file name"}));return}let M=ee.join(p,k);if(!te.existsSync(M)){f.writeHead(404,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:`Spec file not found: ${k}`}));return}let v=te.readFileSync(M,"utf-8");f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,name:k,content:v}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if((E==="/api/specs"||E==="/api/spec")&&x.method==="POST"){try{let g=await V(x),k=(g.name||"").trim();if(!k){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"Missing spec file name"}));return}!k.endsWith(".txt")&&!k.endsWith(".spec")&&(k+=".txt");let M=ee.basename(k);te.existsSync(p)||te.mkdirSync(p,{recursive:!0});let v=ee.join(p,M);te.writeFileSync(v,g.content??"","utf-8"),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,name:M,path:v}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/specs/apply-heals"&&x.method==="POST"){try{let g=await V(x);if(!g.specName||!Array.isArray(g.heals)||g.heals.length===0){f.writeHead(400,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:"Missing specName or heals array"}));return}let k=ee.basename(g.specName),M=ee.join(p,k);if(!te.existsSync(M)){f.writeHead(404,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:`Spec file not found: ${k}`}));return}let v=te.readFileSync(M,"utf-8"),C=0;for(let A of g.heals){if(!A.original||!A.healed||A.original===A.healed)continue;let D=A.original.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),I=new RegExp(`"${D}"`,"g"),H=new RegExp(`'${D}'`,"g");(I.test(v)||H.test(v))&&(v=v.replace(I,`"${A.healed}"`),v=v.replace(H,`'${A.healed}'`),C++)}te.writeFileSync(M,v,"utf-8"),f.writeHead(200,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!0,specName:k,replacementsCount:C,content:v}))}catch(g){f.writeHead(500,{"Content-Type":"application/json; charset=utf-8"}),f.end(JSON.stringify({success:!1,error:g instanceof Error?g.message:String(g)}))}return}if(E==="/api/logcat"&&x.method==="GET"){try{let g=(F.searchParams.get("deviceId")||c.getDefaultDevice())??void 0,k=parseInt(F.searchParams.get("lines")||"200",10),M=isNaN(k)?200:Math.min(Math.max(k,20),1e3),v=F.searchParams.get("filter")?.trim(),A=(await c.getLogcatTail(M,g)).split(`
|