@zibby/cli 0.1.27 → 0.1.29

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.
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
- import ae from"express";import{createServer as Ve}from"http";import{WebSocketServer as Ze}from"ws";import{spawn as Q,execSync as X}from"child_process";import{readFileSync as k,existsSync as l,readdirSync as L,statSync as F,createReadStream as qe,createWriteStream as Qe,mkdirSync as Te,openSync as Xe,readSync as et,closeSync as tt,writeFileSync as ee,unlinkSync as st}from"fs";import{join as a,resolve as x,dirname as Ne,basename as ot}from"path";import{homedir as nt}from"os";import{inspect as rt}from"util";import{fileURLToPath as it,pathToFileURL as ct}from"url";import _e from"open";import at from"dotenv";import{installStudio as lt,isStudioInstalled as ut}from"../utils/studio-installer.js";import{launchStudio as dt}from"../utils/studio-launcher.js";import{mergeSessionRunState as B,listRunningSessionStatesFromSessionsRoot as ft}from"@zibby/core/utils/run-state-session.js";import{liveRunsFromSessionStateRows as pt}from"@zibby/core/utils/session-state-live-runs.js";import{findLatestLiveFrameFileSync as Ae,readLatestLiveFramePayloadSync as ke}from"@zibby/core/utils/live-frame-discovery.js";import{getApiUrl as Ee}from"../config/environments.js";const mt=it(import.meta.url),le=Ne(mt);function Oe(j){const g=a(j,"video.webm");if(l(g))return g;const I=a(j,"execute_live");if(!l(I))return null;try{const E=L(I).filter(S=>S.endsWith(".webm"));return E.length===0?null:E.map(S=>{const T=a(I,S);try{return{p:T,mtime:F(T).mtimeMs}}catch{return{p:T,mtime:0}}}).sort((S,T)=>T.mtime-S.mtime)[0].p}catch{return null}}function xe(j){if(process.platform==="win32")try{const g=X("netstat -ano",{encoding:"utf8"}),I=new Set;for(const E of g.split(`
2
+ import ae from"express";import{createServer as Ve}from"http";import{WebSocketServer as Ze}from"ws";import{spawn as Q,execSync as X}from"child_process";import{readFileSync as k,existsSync as l,readdirSync as L,statSync as F,createReadStream as qe,createWriteStream as Qe,mkdirSync as Te,openSync as Xe,readSync as et,closeSync as tt,writeFileSync as ee,unlinkSync as st}from"fs";import{join as a,resolve as x,dirname as Ne,basename as ot}from"path";import{homedir as nt}from"os";import{inspect as rt}from"util";import{fileURLToPath as it,pathToFileURL as ct}from"url";import Ae from"open";import at from"dotenv";import{promptAndInstallStudio as lt,isStudioInstalled as ut}from"../utils/studio-installer.js";import{launchStudio as dt}from"../utils/studio-launcher.js";import{mergeSessionRunState as B,listRunningSessionStatesFromSessionsRoot as ft}from"@zibby/core/utils/run-state-session.js";import{liveRunsFromSessionStateRows as pt}from"@zibby/core/utils/session-state-live-runs.js";import{findLatestLiveFrameFileSync as _e,readLatestLiveFramePayloadSync as ke}from"@zibby/core/utils/live-frame-discovery.js";import{getApiUrl as Ee}from"../config/environments.js";const mt=it(import.meta.url),le=Ne(mt);function Oe(j){const g=a(j,"video.webm");if(l(g))return g;const I=a(j,"execute_live");if(!l(I))return null;try{const E=L(I).filter(S=>S.endsWith(".webm"));return E.length===0?null:E.map(S=>{const T=a(I,S);try{return{p:T,mtime:F(T).mtimeMs}}catch{return{p:T,mtime:0}}}).sort((S,T)=>T.mtime-S.mtime)[0].p}catch{return null}}function xe(j){if(process.platform==="win32")try{const g=X("netstat -ano",{encoding:"utf8"}),I=new Set;for(const E of g.split(`
3
3
  `)){if(!E.includes("LISTENING"))continue;const w=E.trim().split(/\s+/);if(!(w[1]||"").endsWith(`:${j}`))continue;const T=w[w.length-1];/^\d+$/.test(T)&&I.add(parseInt(T,10))}return[...I].filter(E=>E!==process.pid)}catch{return[]}try{const g=X(`lsof -tiTCP:${j} -sTCP:LISTEN`,{encoding:"utf8"}).trim();return[...new Set(g.split(`
4
- `).filter(Boolean))].map(I=>parseInt(I,10)).filter(I=>!Number.isNaN(I)&&I!==process.pid)}catch{return[]}}async function Ce(j){let g=xe(j);if(g.length!==0){console.log(`[Studio] Port ${j} in use \u2014 stopping previous Studio listener(s): ${g.join(", ")}`);for(const I of g)try{process.kill(I,"SIGTERM")}catch{}await new Promise(I=>setTimeout(I,450)),g=xe(j);for(const I of g)try{process.kill(I,"SIGKILL")}catch{}await new Promise(I=>setTimeout(I,200))}}async function Rt(j={}){const g=ae(),I=Ve(g),E=new Ze({server:I}),w=j.port||3847,S=process.cwd(),T=nt(),G=5173;process.env.DOTENV_CONFIG_QUIET="true";const Re=process.env.NODE_ENV||"development";[x(S,".env.local"),x(S,`.env.${Re}`),x(S,".env")].forEach(s=>{l(s)&&at.config({path:s,override:!1})});const C=new Map,U=new Map;let W=null,$e=null;const J=96e3,ue="studio-cli.log",M="studio-run.json",Fe=".zibby-studio-stop",te=512*1024;g.use(ae.json()),g.get("/api/session-run-states",(s,e)=>{try{let t=O();try{const r=new URL(s.url,"http://zibby.studio").searchParams.get("sessionsRoot");if(r&&String(r).trim()){const c=x(decodeURIComponent(String(r).trim()));l(c)&&F(c).isDirectory()&&(t=c)}}catch{}const n=ft(t),{liveIdList:o,progressByKey:i}=pt(n);e.json({rows:n,liveIdList:o,progressByKey:i,sessionsRoot:t,unavailable:!1})}catch(t){e.status(500).json({error:t.message,rows:[],liveIdList:[],progressByKey:{},unavailable:!0})}}),g.use((s,e,t)=>(e.header("Access-Control-Allow-Origin","*"),e.header("Access-Control-Allow-Methods","GET, POST, PUT, DELETE"),e.header("Access-Control-Allow-Headers","Content-Type"),t()));const z=a(le,"../../../../studio"),se=l(a(z,"package.json"));console.log("[Studio] Checking for source at:",z),console.log("[Studio] Is monorepo:",se);let K=!1;function Le(){const s=a(z,"node_modules",".bin",process.platform==="win32"?"electron.cmd":"electron"),e=l(s),o=Q(e?s:"npx",e?["."]:["electron","."],{cwd:z,detached:!0,stdio:"ignore",shell:!1,env:{...process.env,ZIBBY_STUDIO_PROJECT_ROOT:S,ZIBBY_STUDIO_API_BASE:`http://localhost:${w}/api`}});o.unref(),$e=o}async function de(){if(se){Le();return}ut()||await lt(),dt({port:w,sessionsDir:O()})}if(se){console.log("\u{1F4E6} Detected monorepo - using development mode");try{K=(await fetch(`http://localhost:${G}`)).ok,console.log("\u2705 Found existing Vite dev server")}catch{console.log("\u{1F680} Starting Vite dev server..."),W=Q("npm",["run","dev"],{cwd:z,stdio:"inherit",shell:!0}),await new Promise(e=>{let t=0;const n=40,o=setInterval(async()=>{t++;try{(await fetch(`http://localhost:${G}`)).ok&&(clearInterval(o),K=!0,console.log(`\u2705 Vite dev server ready
5
- `),e())}catch{t>=n&&(clearInterval(o),console.error("\u274C Vite failed to start after 20s"),process.exit(1))}},500)})}}else{const s=a(le,"../../studio-dist");l(s)?(g.use(ae.static(s)),console.log("\u{1F4E6} Production mode: Serving built files")):(console.error("\u274C Error: Studio files not found at:",s),console.error(" Build with: cd studio && npm run build:web"),process.exit(1))}function H(){const s=a(S,".zibby.config.mjs");return l(s)?s:a(T,".zibby.config.mjs")}function O(){H();const s=a(S,".zibby","output","sessions");return l(s)?s:a(T,".zibby","output","sessions")}function oe(s,e){let t=null,n=null,o=null;const i=a(s,"codegen");if(l(i)){const r=a(i,"test.spec.ts"),c=a(i,"generated-test.spec.js");t=l(r)?r:l(c)?c:null;const d=a(i,"test.selenium.py"),p=a(i,"generated-test-selenium.js");n=l(d)?d:l(p)?p:null;const m=a(i,"trace.zip");o=l(m)?m:null}const u=a(s,"generate_script","result.json");if(!t&&l(u))try{const c=JSON.parse(k(u,"utf8"))?.scriptPath;if(typeof c=="string"&&c.trim()){const d=c.trim(),p=d.startsWith("/")||process.platform==="win32"&&/^[A-Za-z]:[\\/]/.test(d)?d:a(e,d);l(p)&&(t=p)}}catch(r){console.warn("[Studio API] generate_script result.json:",r.message)}return!t&&!n&&!o?null:{playwrightFile:t,seleniumFile:n,tracePath:o}}function ze(s,e,t){try{ee(a(s,M),JSON.stringify({sessionId:e,pid:t??null,startedAt:Date.now()},null,2),"utf8")}catch(n){console.error("[Studio API] writeStudioRunMeta:",n.message)}}function D(s){try{const e=a(s,M);l(e)&&st(e)}catch{}}function De(s){const e=Number(s);if(!Number.isFinite(e)||e<=0)return[];try{const t=X(`pgrep -P ${e}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:524288}).trim();return t?t.split(/\n/).map(n=>parseInt(n.trim(),10)).filter(n=>Number.isFinite(n)&&n>0):[]}catch{return[]}}function Y(s,e){const t=Number(s);if(!Number.isFinite(t)||t<=0)return;const n=new Set;function o(i){if(!n.has(i)){n.add(i);for(const u of De(i))o(u);try{process.kill(i,e)}catch{}}}o(t)}function fe(s){const e=Number(s);if(!(!Number.isFinite(e)||e<=0))try{X(`taskkill /PID ${e} /T /F`,{stdio:"ignore",windowsHide:!0})}catch{}}function pe(s){if(!Number.isFinite(s)||s<=0)return;if(process.platform==="win32"){fe(s);return}Y(s,"SIGTERM");const e=setTimeout(()=>{Y(s,"SIGKILL")},800);typeof e.unref=="function"&&e.unref()}function Be(s,e){const n=R(s)||a(O(),s);try{Te(n,{recursive:!0}),ee(a(n,Fe),JSON.stringify({requestedAt:Date.now()}),"utf8")}catch(r){console.error("[Studio API] write studio stop request:",r.message)}const o=C.get(s);if(o){const r=o.pid;if(process.platform==="win32")fe(r);else{Y(r,"SIGTERM");const c=setTimeout(()=>{Y(r,"SIGKILL")},800);typeof c.unref=="function"&&c.unref()}return C.delete(s),D(n),!0}const i=a(n,M);if(l(i)){let r=null;try{const c=JSON.parse(k(i,"utf8"));r=Number(c.pid)}catch(c){console.error("[Studio API] studio-run.json read:",c.message)}if(D(n),Number.isFinite(r)&&r>0)return pe(r),!0}const u=Number(e);return Number.isFinite(u)&&u>0?(pe(u),D(n),!0):!1}function V(s){try{const e=s?.query?.sessionsRoot;if(typeof e!="string")return"";const t=e.trim();if(!t)return"";const n=decodeURIComponent(t),o=x(n);return l(o)?o:""}catch{return""}}function R(s,e=""){const t=typeof s=="string"?decodeURIComponent(s).trim():String(s||"").trim();if(!t)return null;const n=[t],o=t.split("_")[0]?.trim()||"";o&&o!==t&&n.push(o);const i=new Set,u=[],r=new Set,c=[],d=h=>{if(!h)return;const f=x(h);r.has(f)||(r.add(f),c.push(f))},p=h=>{if(h){d(h);for(const f of n){const y=a(h,f);i.has(y)||(i.add(y),u.push(y))}}};p(e),p(O()),p(a(T,".zibby","output","sessions"));let m;try{m=L(S)}catch{m=[]}for(const h of m){if(h.startsWith("."))continue;const f=a(S,h);try{if(!F(f).isDirectory())continue}catch{continue}p(a(f,".zibby","output","sessions"));let y;try{y=L(f,{withFileTypes:!0})}catch{y=[]}for(const _ of y)_?.isDirectory?.()&&String(_.name||"").startsWith(".zibby")&&p(a(f,_.name,"output","sessions"))}const N=u.filter(h=>l(h));if(N.length===0)for(const h of c){if(!l(h))continue;let f;try{f=L(h,{withFileTypes:!0})}catch{f=[]}for(const y of f){if(!y?.isDirectory?.())continue;const _=String(y.name||"");if(_===t||_===o||_.startsWith(`${t}_`)||o&&_.startsWith(`${o}_`)){const $=a(h,_);if(i.has($))continue;i.add($),N.push($)}}}if(N.length===0)return null;if(N.length===1)return N[0];const Z=h=>{let f=0;l(a(h,"execute_live"))&&(f+=20),Ae(h)&&(f+=15),l(a(h,"execute_live","events.json"))&&(f+=8),l(a(h,"execute_live","result.json"))&&(f+=6),l(a(h,M))&&(f+=4),l(a(h,".session-info.json"))&&(f+=2);try{const y=a(h,"execute_live");l(y)&&(f+=Math.min(F(y).mtimeMs/1e12,3))}catch{}return f};return N.sort((h,f)=>Z(f)-Z(h)),N[0]}function Ge(s,e){try{const t=typeof s=="string"?decodeURIComponent(s).trim():String(s||"").trim();if(!t||!e)return null;const n=(t.split("_")[0]||"").trim(),o=Ne(e);if(!n||!o||!l(o))return null;let i=[];try{i=L(o,{withFileTypes:!0})}catch{i=[]}let u=null;for(const r of i){if(!r?.isDirectory?.())continue;const c=String(r.name||"");if(!(c===t||c===n||c.startsWith(`${t}_`)||c.startsWith(`${n}_`)))continue;const d=a(o,c),p=ke(d);p&&(!u||Number(p.mtime||0)>Number(u.mtime||0))&&(u=p)}return u}catch{return null}}g.get("/api/config/check",(s,e)=>{const t=H();e.json({exists:l(t),path:t,isProjectLevel:t.startsWith(S)})}),g.get("/api/projects",(s,e)=>{try{const t=a(T,".zibby","config.json");if(!l(t))return e.json({projects:[]});const n=JSON.parse(k(t,"utf8")),o=typeof n.sessionToken=="string"&&n.sessionToken.trim()!==""?n.sessionToken.trim():"",i=Array.isArray(n.projects)?n.projects:[];if(!o)return e.json({projects:i});const u=String(Ee()).replace(/\/$/,"");fetch(`${u}/projects`,{headers:{Authorization:`Bearer ${o}`}}).then(r=>r.json().catch(()=>({})).then(c=>({ok:r.ok,status:r.status,body:c}))).then(({ok:r,body:c})=>{if(!r)return e.json({projects:i});const p=(Array.isArray(c?.projects)?c.projects:[]).map(m=>({name:m?.name,projectId:m?.projectId,apiToken:m?.apiToken||null,createdAt:m?.createdAt||null,updatedAt:m?.updatedAt||null}));n.projects=p;try{ee(t,`${JSON.stringify(n,null,2)}
4
+ `).filter(Boolean))].map(I=>parseInt(I,10)).filter(I=>!Number.isNaN(I)&&I!==process.pid)}catch{return[]}}async function Ce(j){let g=xe(j);if(g.length!==0){console.log(`[Studio] Port ${j} in use \u2014 stopping previous Studio listener(s): ${g.join(", ")}`);for(const I of g)try{process.kill(I,"SIGTERM")}catch{}await new Promise(I=>setTimeout(I,450)),g=xe(j);for(const I of g)try{process.kill(I,"SIGKILL")}catch{}await new Promise(I=>setTimeout(I,200))}}async function Rt(j={}){const g=ae(),I=Ve(g),E=new Ze({server:I}),w=j.port||3847,S=process.cwd(),T=nt(),G=5173;process.env.DOTENV_CONFIG_QUIET="true";const Re=process.env.NODE_ENV||"development";[x(S,".env.local"),x(S,`.env.${Re}`),x(S,".env")].forEach(s=>{l(s)&&at.config({path:s,override:!1})});const C=new Map,U=new Map;let W=null,$e=null;const J=96e3,ue="studio-cli.log",M="studio-run.json",Fe=".zibby-studio-stop",te=512*1024;g.use(ae.json()),g.get("/api/session-run-states",(s,e)=>{try{let t=O();try{const r=new URL(s.url,"http://zibby.studio").searchParams.get("sessionsRoot");if(r&&String(r).trim()){const c=x(decodeURIComponent(String(r).trim()));l(c)&&F(c).isDirectory()&&(t=c)}}catch{}const n=ft(t),{liveIdList:o,progressByKey:i}=pt(n);e.json({rows:n,liveIdList:o,progressByKey:i,sessionsRoot:t,unavailable:!1})}catch(t){e.status(500).json({error:t.message,rows:[],liveIdList:[],progressByKey:{},unavailable:!0})}}),g.use((s,e,t)=>(e.header("Access-Control-Allow-Origin","*"),e.header("Access-Control-Allow-Methods","GET, POST, PUT, DELETE"),e.header("Access-Control-Allow-Headers","Content-Type"),t()));const z=a(le,"../../../../studio"),se=l(a(z,"package.json"));console.log("[Studio] Checking for source at:",z),console.log("[Studio] Is monorepo:",se);let K=!1;function Le(){const s=a(z,"node_modules",".bin",process.platform==="win32"?"electron.cmd":"electron"),e=l(s),o=Q(e?s:"npx",e?["."]:["electron","."],{cwd:z,detached:!0,stdio:"ignore",shell:!1,env:{...process.env,ZIBBY_STUDIO_PROJECT_ROOT:S,ZIBBY_STUDIO_API_BASE:`http://localhost:${w}/api`}});o.unref(),$e=o}async function de(){if(se){Le();return}!ut()&&!await lt()||dt({port:w,sessionsDir:O()})}if(se){console.log("\u{1F4E6} Detected monorepo - using development mode");try{K=(await fetch(`http://localhost:${G}`)).ok,console.log("\u2705 Found existing Vite dev server")}catch{console.log("\u{1F680} Starting Vite dev server..."),W=Q("npm",["run","dev"],{cwd:z,stdio:"inherit",shell:!0}),await new Promise(e=>{let t=0;const n=40,o=setInterval(async()=>{t++;try{(await fetch(`http://localhost:${G}`)).ok&&(clearInterval(o),K=!0,console.log(`\u2705 Vite dev server ready
5
+ `),e())}catch{t>=n&&(clearInterval(o),console.error("\u274C Vite failed to start after 20s"),process.exit(1))}},500)})}}else{const s=a(le,"../../studio-dist");l(s)?(g.use(ae.static(s)),console.log("\u{1F4E6} Production mode: Serving built files")):(console.error("\u274C Error: Studio files not found at:",s),console.error(" Build with: cd studio && npm run build:web"),process.exit(1))}function H(){const s=a(S,".zibby.config.mjs");return l(s)?s:a(T,".zibby.config.mjs")}function O(){H();const s=a(S,".zibby","output","sessions");return l(s)?s:a(T,".zibby","output","sessions")}function oe(s,e){let t=null,n=null,o=null;const i=a(s,"codegen");if(l(i)){const r=a(i,"test.spec.ts"),c=a(i,"generated-test.spec.js");t=l(r)?r:l(c)?c:null;const d=a(i,"test.selenium.py"),p=a(i,"generated-test-selenium.js");n=l(d)?d:l(p)?p:null;const m=a(i,"trace.zip");o=l(m)?m:null}const u=a(s,"generate_script","result.json");if(!t&&l(u))try{const c=JSON.parse(k(u,"utf8"))?.scriptPath;if(typeof c=="string"&&c.trim()){const d=c.trim(),p=d.startsWith("/")||process.platform==="win32"&&/^[A-Za-z]:[\\/]/.test(d)?d:a(e,d);l(p)&&(t=p)}}catch(r){console.warn("[Studio API] generate_script result.json:",r.message)}return!t&&!n&&!o?null:{playwrightFile:t,seleniumFile:n,tracePath:o}}function ze(s,e,t){try{ee(a(s,M),JSON.stringify({sessionId:e,pid:t??null,startedAt:Date.now()},null,2),"utf8")}catch(n){console.error("[Studio API] writeStudioRunMeta:",n.message)}}function D(s){try{const e=a(s,M);l(e)&&st(e)}catch{}}function De(s){const e=Number(s);if(!Number.isFinite(e)||e<=0)return[];try{const t=X(`pgrep -P ${e}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:524288}).trim();return t?t.split(/\n/).map(n=>parseInt(n.trim(),10)).filter(n=>Number.isFinite(n)&&n>0):[]}catch{return[]}}function Y(s,e){const t=Number(s);if(!Number.isFinite(t)||t<=0)return;const n=new Set;function o(i){if(!n.has(i)){n.add(i);for(const u of De(i))o(u);try{process.kill(i,e)}catch{}}}o(t)}function fe(s){const e=Number(s);if(!(!Number.isFinite(e)||e<=0))try{X(`taskkill /PID ${e} /T /F`,{stdio:"ignore",windowsHide:!0})}catch{}}function pe(s){if(!Number.isFinite(s)||s<=0)return;if(process.platform==="win32"){fe(s);return}Y(s,"SIGTERM");const e=setTimeout(()=>{Y(s,"SIGKILL")},800);typeof e.unref=="function"&&e.unref()}function Be(s,e){const n=R(s)||a(O(),s);try{Te(n,{recursive:!0}),ee(a(n,Fe),JSON.stringify({requestedAt:Date.now()}),"utf8")}catch(r){console.error("[Studio API] write studio stop request:",r.message)}const o=C.get(s);if(o){const r=o.pid;if(process.platform==="win32")fe(r);else{Y(r,"SIGTERM");const c=setTimeout(()=>{Y(r,"SIGKILL")},800);typeof c.unref=="function"&&c.unref()}return C.delete(s),D(n),!0}const i=a(n,M);if(l(i)){let r=null;try{const c=JSON.parse(k(i,"utf8"));r=Number(c.pid)}catch(c){console.error("[Studio API] studio-run.json read:",c.message)}if(D(n),Number.isFinite(r)&&r>0)return pe(r),!0}const u=Number(e);return Number.isFinite(u)&&u>0?(pe(u),D(n),!0):!1}function V(s){try{const e=s?.query?.sessionsRoot;if(typeof e!="string")return"";const t=e.trim();if(!t)return"";const n=decodeURIComponent(t),o=x(n);return l(o)?o:""}catch{return""}}function R(s,e=""){const t=typeof s=="string"?decodeURIComponent(s).trim():String(s||"").trim();if(!t)return null;const n=[t],o=t.split("_")[0]?.trim()||"";o&&o!==t&&n.push(o);const i=new Set,u=[],r=new Set,c=[],d=h=>{if(!h)return;const f=x(h);r.has(f)||(r.add(f),c.push(f))},p=h=>{if(h){d(h);for(const f of n){const y=a(h,f);i.has(y)||(i.add(y),u.push(y))}}};p(e),p(O()),p(a(T,".zibby","output","sessions"));let m;try{m=L(S)}catch{m=[]}for(const h of m){if(h.startsWith("."))continue;const f=a(S,h);try{if(!F(f).isDirectory())continue}catch{continue}p(a(f,".zibby","output","sessions"));let y;try{y=L(f,{withFileTypes:!0})}catch{y=[]}for(const A of y)A?.isDirectory?.()&&String(A.name||"").startsWith(".zibby")&&p(a(f,A.name,"output","sessions"))}const N=u.filter(h=>l(h));if(N.length===0)for(const h of c){if(!l(h))continue;let f;try{f=L(h,{withFileTypes:!0})}catch{f=[]}for(const y of f){if(!y?.isDirectory?.())continue;const A=String(y.name||"");if(A===t||A===o||A.startsWith(`${t}_`)||o&&A.startsWith(`${o}_`)){const $=a(h,A);if(i.has($))continue;i.add($),N.push($)}}}if(N.length===0)return null;if(N.length===1)return N[0];const Z=h=>{let f=0;l(a(h,"execute_live"))&&(f+=20),_e(h)&&(f+=15),l(a(h,"execute_live","events.json"))&&(f+=8),l(a(h,"execute_live","result.json"))&&(f+=6),l(a(h,M))&&(f+=4),l(a(h,".session-info.json"))&&(f+=2);try{const y=a(h,"execute_live");l(y)&&(f+=Math.min(F(y).mtimeMs/1e12,3))}catch{}return f};return N.sort((h,f)=>Z(f)-Z(h)),N[0]}function Ge(s,e){try{const t=typeof s=="string"?decodeURIComponent(s).trim():String(s||"").trim();if(!t||!e)return null;const n=(t.split("_")[0]||"").trim(),o=Ne(e);if(!n||!o||!l(o))return null;let i=[];try{i=L(o,{withFileTypes:!0})}catch{i=[]}let u=null;for(const r of i){if(!r?.isDirectory?.())continue;const c=String(r.name||"");if(!(c===t||c===n||c.startsWith(`${t}_`)||c.startsWith(`${n}_`)))continue;const d=a(o,c),p=ke(d);p&&(!u||Number(p.mtime||0)>Number(u.mtime||0))&&(u=p)}return u}catch{return null}}g.get("/api/config/check",(s,e)=>{const t=H();e.json({exists:l(t),path:t,isProjectLevel:t.startsWith(S)})}),g.get("/api/projects",(s,e)=>{try{const t=a(T,".zibby","config.json");if(!l(t))return e.json({projects:[]});const n=JSON.parse(k(t,"utf8")),o=typeof n.sessionToken=="string"&&n.sessionToken.trim()!==""?n.sessionToken.trim():"",i=Array.isArray(n.projects)?n.projects:[];if(!o)return e.json({projects:i});const u=String(Ee()).replace(/\/$/,"");fetch(`${u}/projects`,{headers:{Authorization:`Bearer ${o}`}}).then(r=>r.json().catch(()=>({})).then(c=>({ok:r.ok,status:r.status,body:c}))).then(({ok:r,body:c})=>{if(!r)return e.json({projects:i});const p=(Array.isArray(c?.projects)?c.projects:[]).map(m=>({name:m?.name,projectId:m?.projectId,apiToken:m?.apiToken||null,createdAt:m?.createdAt||null,updatedAt:m?.updatedAt||null}));n.projects=p;try{ee(t,`${JSON.stringify(n,null,2)}
6
6
  `,"utf8")}catch{}return e.json({projects:p})}).catch(()=>e.json({projects:i}))}catch(t){e.status(500).json({error:t.message})}}),g.post("/api/projects/create",async(s,e)=>{try{const t=typeof s.body?.name=="string"?s.body.name.trim():"";if(!t)return e.status(400).json({error:"Project name is required"});const n=a(T,".zibby","config.json");if(!l(n))return e.status(401).json({error:"Not logged in"});const o=JSON.parse(k(n,"utf8")),i=typeof o.sessionToken=="string"&&o.sessionToken.trim()!==""?o.sessionToken.trim():"";if(!i)return e.status(401).json({error:"Not logged in"});const u=String(Ee()).replace(/\/$/,""),r=await fetch(`${u}/projects`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`},body:JSON.stringify({name:t})}),c=await r.json().catch(()=>({}));if(!r.ok)return e.status(r.status).json({error:c.error||c.message||`HTTP ${r.status}`});const d=c?.project&&typeof c.project=="object"?c.project:null;if(d?.projectId){const m=(Array.isArray(o.projects)?o.projects:[]).filter(N=>String(N?.projectId||"")!==String(d.projectId));m.push({name:d.name||t,projectId:d.projectId,apiToken:d.apiToken||c?.apiToken||null,createdAt:d.createdAt||null,updatedAt:d.updatedAt||null}),o.projects=m;try{ee(n,`${JSON.stringify(o,null,2)}
7
- `,"utf8")}catch{}}return e.json(c)}catch(t){return e.status(500).json({error:t.message||String(t)})}});function Ue(s,e){const t=Array.isArray(e)?e.filter(d=>typeof d=="string"):[],n=process.argv[1]?x(process.argv[1]):"",o=n?ot(n):"";let i;try{i=!!n&&l(n)&&F(n).isFile()&&!/\.(cmd|ps1|bat)$/i.test(n)&&(/\.(m|c)?js$/i.test(n)||o==="zibby")}catch{i=!1}if(i){const d=[n,"test",s,...t],p=[process.execPath,...d].map(m=>/\s/.test(String(m))?`"${String(m).replace(/"/g,'\\"')}"`:m).join(" ");return{useShell:!1,cmd:process.execPath,args:d,display:p}}const u=s.replace(/"/g,'\\"'),r=t.join(" "),c=`zibby test "${u}" ${r}`.trim();return{useShell:!0,cmd:c,args:[],display:c}}g.get("/api/run/result/:sessionId",(s,e)=>{const{sessionId:t}=s.params,n=U.get(t);if(!n)return e.status(404).json({pending:!0,error:"unknown_session"});e.json(n)}),g.post("/api/run",async(s,e)=>{const{task:t,args:n=[],sessionId:o,studioTestCaseId:i}=s.body;if(!t||!o)return e.status(400).json({success:!1,error:"task and sessionId are required"});const u=O(),r=a(u,o),c=x(r),d=i!=null&&String(i).trim()!==""?String(i).trim():String(o);U.set(o,{pending:!0});let p=!1,m=null;const N=()=>{if(m&&!m.destroyed)try{m.end()}catch{}m=null},Z=(f,y)=>{U.delete(o),N(),p||e.status(f).json(y)},h=()=>{const f=[];return E.clients.forEach(y=>{y.sessionId===o&&f.push(y)}),f};try{const f=Ue(t,n);console.log("[Studio API] Running:",f.display),f.useShell?console.warn('[Studio API] Falling back to shell "zibby" from PATH \u2014 if runs do nothing, run Studio from this repo or ensure `zibby` points to the same install.'):console.log("[Studio API] Using same CLI as this server (argv[1]):",process.argv[1]),Te(r,{recursive:!0}),m=Qe(a(r,ue),{flags:"w"}),B(c,{sessionId:String(o),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:S,outputBase:".zibby/output",sessionPathAbs:c});const y=Q(f.cmd,f.args,{cwd:S,shell:f.useShell,env:{...process.env,ZIBBY_SESSION_ID:o,ZIBBY_SESSION_PATH:x(r)}});C.set(o,y),ze(r,o,y.pid),B(c,{sessionId:String(o),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:S,outputBase:".zibby/output",sessionPathAbs:c,pid:y.pid??null});let _="",$="",ye=!1,Se=0;const Ie=setInterval(()=>{try{const b=Ae(r);if(!b||b.mtime<=Se)return;Se=b.mtime;const P=k(b.p).toString("base64"),v=b.p.toLowerCase().endsWith(".png")?"image/png":"image/jpeg";h().forEach(q=>{try{q.send(JSON.stringify({type:"video-frame",sessionId:o,frame:P,mime:v}))}catch{}})}catch{}},320),be=b=>{if(ye)return;ye=!0,N();const P={pending:!1,...b};typeof P.stdout=="string"&&P.stdout.length>J&&(P.stdout=`\u2026(truncated)
7
+ `,"utf8")}catch{}}return e.json(c)}catch(t){return e.status(500).json({error:t.message||String(t)})}});function Ue(s,e){const t=Array.isArray(e)?e.filter(d=>typeof d=="string"):[],n=process.argv[1]?x(process.argv[1]):"",o=n?ot(n):"";let i;try{i=!!n&&l(n)&&F(n).isFile()&&!/\.(cmd|ps1|bat)$/i.test(n)&&(/\.(m|c)?js$/i.test(n)||o==="zibby")}catch{i=!1}if(i){const d=[n,"test",s,...t],p=[process.execPath,...d].map(m=>/\s/.test(String(m))?`"${String(m).replace(/"/g,'\\"')}"`:m).join(" ");return{useShell:!1,cmd:process.execPath,args:d,display:p}}const u=s.replace(/"/g,'\\"'),r=t.join(" "),c=`zibby test "${u}" ${r}`.trim();return{useShell:!0,cmd:c,args:[],display:c}}g.get("/api/run/result/:sessionId",(s,e)=>{const{sessionId:t}=s.params,n=U.get(t);if(!n)return e.status(404).json({pending:!0,error:"unknown_session"});e.json(n)}),g.post("/api/run",async(s,e)=>{const{task:t,args:n=[],sessionId:o,studioTestCaseId:i}=s.body;if(!t||!o)return e.status(400).json({success:!1,error:"task and sessionId are required"});const u=O(),r=a(u,o),c=x(r),d=i!=null&&String(i).trim()!==""?String(i).trim():String(o);U.set(o,{pending:!0});let p=!1,m=null;const N=()=>{if(m&&!m.destroyed)try{m.end()}catch{}m=null},Z=(f,y)=>{U.delete(o),N(),p||e.status(f).json(y)},h=()=>{const f=[];return E.clients.forEach(y=>{y.sessionId===o&&f.push(y)}),f};try{const f=Ue(t,n);console.log("[Studio API] Running:",f.display),f.useShell?console.warn('[Studio API] Falling back to shell "zibby" from PATH \u2014 if runs do nothing, run Studio from this repo or ensure `zibby` points to the same install.'):console.log("[Studio API] Using same CLI as this server (argv[1]):",process.argv[1]),Te(r,{recursive:!0}),m=Qe(a(r,ue),{flags:"w"}),B(c,{sessionId:String(o),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:S,outputBase:".zibby/output",sessionPathAbs:c});const y=Q(f.cmd,f.args,{cwd:S,shell:f.useShell,env:{...process.env,ZIBBY_SESSION_ID:o,ZIBBY_SESSION_PATH:x(r)}});C.set(o,y),ze(r,o,y.pid),B(c,{sessionId:String(o),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:S,outputBase:".zibby/output",sessionPathAbs:c,pid:y.pid??null});let A="",$="",ye=!1,Se=0;const Ie=setInterval(()=>{try{const b=_e(r);if(!b||b.mtime<=Se)return;Se=b.mtime;const P=k(b.p).toString("base64"),v=b.p.toLowerCase().endsWith(".png")?"image/png":"image/jpeg";h().forEach(q=>{try{q.send(JSON.stringify({type:"video-frame",sessionId:o,frame:P,mime:v}))}catch{}})}catch{}},320),be=b=>{if(ye)return;ye=!0,N();const P={pending:!1,...b};typeof P.stdout=="string"&&P.stdout.length>J&&(P.stdout=`\u2026(truncated)
8
8
  ${P.stdout.slice(-J)}`),typeof P.stderr=="string"&&P.stderr.length>J&&(P.stderr=`\u2026(truncated)
9
- ${P.stderr.slice(-J)}`),U.set(o,P),h().forEach(v=>{try{v.send(JSON.stringify({type:"run-complete",sessionId:o,result:b}))}catch{}})};y.stdout.on("data",b=>{const P=b.toString();if(_+=P,m&&!m.destroyed)try{m.write(P)}catch(v){console.error("[Studio API] studio-cli.log write failed:",v.message)}h().forEach(v=>{try{v.send(JSON.stringify({type:"stdout",data:P}))}catch{}})}),y.stderr.on("data",b=>{const P=b.toString();if($+=P,m&&!m.destroyed)try{m.write(`[stderr] ${P}`)}catch(v){console.error("[Studio API] studio-cli.log write failed:",v.message)}h().forEach(v=>{try{v.send(JSON.stringify({type:"stderr",data:P}))}catch{}})}),y.on("error",b=>{clearInterval(Ie),C.delete(o),B(c,{sessionId:String(o),studioTestCaseId:d,status:"failed",runSource:"studio",activeNode:null,activeStageIndex:null,errorMessage:b.message,cwd:S,outputBase:".zibby/output",sessionPathAbs:c}),console.error("[Studio API] Spawn error:",b.message),be({heldUntilExit:!0,success:!1,exitCode:null,error:b.message,stderr:$,stdout:_,runName:null,videoPath:null,eventsPath:null,codegenFiles:null,metadata:{sessionId:o,task:t}})}),y.on("close",b=>{clearInterval(Ie),C.delete(o),B(c,{sessionId:String(o),studioTestCaseId:d,status:b===0?"completed":b===130||b===143?"interrupted":"failed",runSource:"studio",activeNode:null,activeStageIndex:null,exitCode:b,cwd:S,outputBase:".zibby/output",sessionPathAbs:c}),D(r),h().forEach(A=>{try{A.send(JSON.stringify({type:"exit",code:b}))}catch{}});const P=b===0;let v=null;const q=a(r,".session-info.json"),Pe=a(r,"result.json");if(l(q))try{const A=JSON.parse(k(q,"utf8"));v=A.name||A.task}catch(A){console.error("[Studio API] Failed to read session info:",A.message)}else if(l(Pe))try{const A=JSON.parse(k(Pe,"utf8"));v=A.name||A.task}catch(A){console.error("[Studio API] Failed to read result:",A.message)}const He=[/\[SessionRecorder\] Run name set: (.+)/,/Run name: (.+)/];if(!v)for(const A of He){const we=_.match(A);if(we){v=we[1].trim();break}}const Ye=oe(r,S),ie=Oe(r),ve=a(r,"events.json"),je=a(r,"execute_live","events.json"),ce=l(ve)?ve:l(je)?je:null;be({heldUntilExit:!0,success:P,exitCode:b,stdout:_,stderr:$,runName:v,videoPath:ie&&l(ie)?ie:null,eventsPath:ce&&l(ce)?ce:null,codegenFiles:Ye,metadata:{sessionId:o,task:t}})}),p=!0,e.status(202).json({accepted:!0,sessionId:o,pid:y.pid!=null&&Number.isFinite(Number(y.pid))?Number(y.pid):null,pollPath:`/api/run/result/${o}`,message:"Run started. Poll GET /api/run/result/:sessionId until pending is false."})}catch(f){Z(500,{success:!1,error:f.message})}}),g.post("/api/stop/:sessionId",(s,e)=>{const{sessionId:t}=s.params,n=s.body&&s.body.pid!=null?s.body.pid:null;if(Be(t,n))return e.json({success:!0});e.status(404).json({success:!1,error:"Session not found"})}),g.get("/api/recordings",(s,e)=>{try{const t=O();if(!l(t))return e.json({recordings:[],path:t});const o=L(t).map(i=>{const u=a(t,i),r=a(u,".session-info.json"),c=a(u,"result.json");let d={};return l(r)?d=JSON.parse(k(r,"utf8")):l(c)&&(d=JSON.parse(k(c,"utf8"))),{sessionId:i,...d,path:u}});e.json({path:t,recordings:o})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/cli-log",(s,e)=>{try{const{sessionId:t}=s.params;let n=parseInt(String(s.query.offset||"0"),10);(Number.isNaN(n)||n<0)&&(n=0);const o=V(s),i=R(t,o),u=a(i||a(O(),t),ue);if(!l(u))return e.json({exists:!1,content:"",size:0,fromByte:0,nextOffset:0});const r=F(u).size;n>r&&(n=r);let c=n,d=r-c;d>te&&(c=r-te,d=te);const p=Buffer.alloc(d),m=Xe(u,"r");try{et(m,p,0,d,c)}finally{tt(m)}e.json({exists:!0,content:p.toString("utf8"),size:r,fromByte:c,nextOffset:r})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/events",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.json({events:[]});let i=a(o,"events.json");if(!l(i)){const r=a(o,"execute_live","events.json");if(l(r))i=r;else return e.json({events:[]})}const u=JSON.parse(k(i,"utf8"));e.json({events:u})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/live-preview",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.json({ok:!1,error:"session_not_found"});const u=ke(o)||Ge(t,o);if(!u)return e.json({ok:!1,error:"no_frame",sessionId:t,sessionPath:o});e.json({ok:!0,frame:u.base64,mime:u.mime,mtime:u.mtime,path:u.path})}catch(t){e.status(500).json({ok:!1,error:t.message})}}),g.get("/api/sessions/:sessionId/video",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.status(404).json({error:"Session not found"});const i=Oe(o);if(!i||!l(i))return e.status(404).json({error:"Video not found"});const u=F(i);e.writeHead(200,{"Content-Type":"video/webm","Content-Length":u.size}),qe(i).pipe(e)}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/codegen/playwright",(s,e)=>{try{const{sessionId:t}=s.params,n=R(t)||a(O(),t);if(!l(n))return e.status(404).type("text/plain").send("Session not found");const o=oe(n,S);if(!o?.playwrightFile||!l(o.playwrightFile))return e.status(404).type("text/plain").send("No Playwright artifact");e.type("text/plain; charset=utf-8").send(k(o.playwrightFile,"utf8"))}catch(t){e.status(500).type("text/plain").send(t.message)}}),g.get("/api/sessions/:sessionId/codegen/selenium",(s,e)=>{try{const{sessionId:t}=s.params,n=R(t)||a(O(),t);if(!l(n))return e.status(404).type("text/plain").send("Session not found");const o=oe(n,S);if(!o?.seleniumFile||!l(o.seleniumFile))return e.status(404).type("text/plain").send("No Selenium artifact");e.type("text/plain; charset=utf-8").send(k(o.seleniumFile,"utf8"))}catch(t){e.status(500).type("text/plain").send(t.message)}}),g.post("/api/init",async(s,e)=>{const{agentType:t,apiKey:n}=s.body;try{const o=`zibby init --agent ${t} --headed`,i=Q(o,[],{cwd:S,env:{...process.env,CURSOR_API_KEY:t==="cursor"?n:process.env.CURSOR_API_KEY,ANTHROPIC_API_KEY:t==="claude"?n:process.env.ANTHROPIC_API_KEY,OPENAI_API_KEY:t==="codex"?n:process.env.OPENAI_API_KEY,GEMINI_API_KEY:t==="gemini"?n:process.env.GEMINI_API_KEY,GOOGLE_API_KEY:t==="gemini"?n:process.env.GOOGLE_API_KEY,CI:"true",ZIBBY_CI:"true"},shell:!0});let u="",r="";i.stdout.on("data",p=>{u+=p.toString()}),i.stderr.on("data",p=>{r+=p.toString()});let c=!1;const d=(p,m)=>{if(!c)if(c=!0,p===0)e.json({success:!0,stdout:u});else{const N=m||r||"zibby init failed";e.status(500).json({success:!1,error:N,stdout:u})}};i.on("close",p=>d(p)),i.on("error",p=>d(1,p.message))}catch(o){e.status(500).json({success:!1,error:o.message})}});const ne=(s,e)=>{const t={type:s,message:e,time:new Date().toLocaleTimeString()};E.clients.forEach(n=>{if(n.listenType==="console")try{n.send(JSON.stringify(t))}catch{}})},We=console.log,Je=console.error,Me=console.warn,re=s=>s.map(e=>{if(e==null)return String(e);if(typeof e=="object")try{return rt(e,{depth:4,colors:!1,breakLength:100})}catch{return String(e)}return String(e)}).join(" ");console.log=(...s)=>{We(...s),ne("info",re(s))},console.error=(...s)=>{Je(...s),ne("error",re(s))},console.warn=(...s)=>{Me(...s),ne("warn",re(s))},E.on("connection",(s,e)=>{const t=e.url;if(t.includes("/console"))s.listenType="console",console.log("[WebSocket] Console listener connected"),s.send(JSON.stringify({type:"success",message:"\u{1F3AD} Connected to Studio server logs",time:new Date().toLocaleTimeString()}));else{let n=t.split("/").pop()||"";try{n=decodeURIComponent(n)}catch{}s.sessionId=n,s.listenType="session",console.log(`[WebSocket] Session listener connected: ${n}`)}s.on("close",()=>{s.listenType==="console"?console.log("[WebSocket] Console listener disconnected"):console.log(`[WebSocket] Session listener disconnected: ${s.sessionId}`)})}),g.get("/api/workflow/graph",async(s,e)=>{try{const t=a(S,".zibby","graph.mjs");if(!l(t))return console.warn("[Workflow Graph API] Graph file not found at",t),e.json({graph:null,error:"No graph.mjs found in .zibby folder"});const n=await import(`${ct(t).href}?t=${Date.now()}`),o=n.default||Object.values(n)[0];if(!o||typeof o!="function")return console.error("[Workflow Graph API] Invalid graph module, got:",typeof o),e.json({graph:null,error:"Invalid graph module"});const u=new o().buildGraph(),r=u.toJSON?u.toJSON():u.serialize();console.log(`[Workflow Graph API] graph OK nodes=${r.nodes?.length??0} edges=${r.edges?.length??0} (${t})`),e.json({graph:r})}catch(t){console.error("[Workflow Graph API] Failed to load workflow graph:",t),e.status(500).json({graph:null,error:t.message})}}),K||g.get("*",(s,e)=>{const t=a(le,"../../studio-dist/index.html");l(t)?e.sendFile(t):e.status(404).send("Studio not built. Run: cd studio && npm run build:web")});const Ke=()=>{K?(console.log(`
9
+ ${P.stderr.slice(-J)}`),U.set(o,P),h().forEach(v=>{try{v.send(JSON.stringify({type:"run-complete",sessionId:o,result:b}))}catch{}})};y.stdout.on("data",b=>{const P=b.toString();if(A+=P,m&&!m.destroyed)try{m.write(P)}catch(v){console.error("[Studio API] studio-cli.log write failed:",v.message)}h().forEach(v=>{try{v.send(JSON.stringify({type:"stdout",data:P}))}catch{}})}),y.stderr.on("data",b=>{const P=b.toString();if($+=P,m&&!m.destroyed)try{m.write(`[stderr] ${P}`)}catch(v){console.error("[Studio API] studio-cli.log write failed:",v.message)}h().forEach(v=>{try{v.send(JSON.stringify({type:"stderr",data:P}))}catch{}})}),y.on("error",b=>{clearInterval(Ie),C.delete(o),B(c,{sessionId:String(o),studioTestCaseId:d,status:"failed",runSource:"studio",activeNode:null,activeStageIndex:null,errorMessage:b.message,cwd:S,outputBase:".zibby/output",sessionPathAbs:c}),console.error("[Studio API] Spawn error:",b.message),be({heldUntilExit:!0,success:!1,exitCode:null,error:b.message,stderr:$,stdout:A,runName:null,videoPath:null,eventsPath:null,codegenFiles:null,metadata:{sessionId:o,task:t}})}),y.on("close",b=>{clearInterval(Ie),C.delete(o),B(c,{sessionId:String(o),studioTestCaseId:d,status:b===0?"completed":b===130||b===143?"interrupted":"failed",runSource:"studio",activeNode:null,activeStageIndex:null,exitCode:b,cwd:S,outputBase:".zibby/output",sessionPathAbs:c}),D(r),h().forEach(_=>{try{_.send(JSON.stringify({type:"exit",code:b}))}catch{}});const P=b===0;let v=null;const q=a(r,".session-info.json"),Pe=a(r,"result.json");if(l(q))try{const _=JSON.parse(k(q,"utf8"));v=_.name||_.task}catch(_){console.error("[Studio API] Failed to read session info:",_.message)}else if(l(Pe))try{const _=JSON.parse(k(Pe,"utf8"));v=_.name||_.task}catch(_){console.error("[Studio API] Failed to read result:",_.message)}const He=[/\[SessionRecorder\] Run name set: (.+)/,/Run name: (.+)/];if(!v)for(const _ of He){const we=A.match(_);if(we){v=we[1].trim();break}}const Ye=oe(r,S),ie=Oe(r),ve=a(r,"events.json"),je=a(r,"execute_live","events.json"),ce=l(ve)?ve:l(je)?je:null;be({heldUntilExit:!0,success:P,exitCode:b,stdout:A,stderr:$,runName:v,videoPath:ie&&l(ie)?ie:null,eventsPath:ce&&l(ce)?ce:null,codegenFiles:Ye,metadata:{sessionId:o,task:t}})}),p=!0,e.status(202).json({accepted:!0,sessionId:o,pid:y.pid!=null&&Number.isFinite(Number(y.pid))?Number(y.pid):null,pollPath:`/api/run/result/${o}`,message:"Run started. Poll GET /api/run/result/:sessionId until pending is false."})}catch(f){Z(500,{success:!1,error:f.message})}}),g.post("/api/stop/:sessionId",(s,e)=>{const{sessionId:t}=s.params,n=s.body&&s.body.pid!=null?s.body.pid:null;if(Be(t,n))return e.json({success:!0});e.status(404).json({success:!1,error:"Session not found"})}),g.get("/api/recordings",(s,e)=>{try{const t=O();if(!l(t))return e.json({recordings:[],path:t});const o=L(t).map(i=>{const u=a(t,i),r=a(u,".session-info.json"),c=a(u,"result.json");let d={};return l(r)?d=JSON.parse(k(r,"utf8")):l(c)&&(d=JSON.parse(k(c,"utf8"))),{sessionId:i,...d,path:u}});e.json({path:t,recordings:o})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/cli-log",(s,e)=>{try{const{sessionId:t}=s.params;let n=parseInt(String(s.query.offset||"0"),10);(Number.isNaN(n)||n<0)&&(n=0);const o=V(s),i=R(t,o),u=a(i||a(O(),t),ue);if(!l(u))return e.json({exists:!1,content:"",size:0,fromByte:0,nextOffset:0});const r=F(u).size;n>r&&(n=r);let c=n,d=r-c;d>te&&(c=r-te,d=te);const p=Buffer.alloc(d),m=Xe(u,"r");try{et(m,p,0,d,c)}finally{tt(m)}e.json({exists:!0,content:p.toString("utf8"),size:r,fromByte:c,nextOffset:r})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/events",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.json({events:[]});let i=a(o,"events.json");if(!l(i)){const r=a(o,"execute_live","events.json");if(l(r))i=r;else return e.json({events:[]})}const u=JSON.parse(k(i,"utf8"));e.json({events:u})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/live-preview",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.json({ok:!1,error:"session_not_found"});const u=ke(o)||Ge(t,o);if(!u)return e.json({ok:!1,error:"no_frame",sessionId:t,sessionPath:o});e.json({ok:!0,frame:u.base64,mime:u.mime,mtime:u.mtime,path:u.path})}catch(t){e.status(500).json({ok:!1,error:t.message})}}),g.get("/api/sessions/:sessionId/video",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.status(404).json({error:"Session not found"});const i=Oe(o);if(!i||!l(i))return e.status(404).json({error:"Video not found"});const u=F(i);e.writeHead(200,{"Content-Type":"video/webm","Content-Length":u.size}),qe(i).pipe(e)}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/codegen/playwright",(s,e)=>{try{const{sessionId:t}=s.params,n=R(t)||a(O(),t);if(!l(n))return e.status(404).type("text/plain").send("Session not found");const o=oe(n,S);if(!o?.playwrightFile||!l(o.playwrightFile))return e.status(404).type("text/plain").send("No Playwright artifact");e.type("text/plain; charset=utf-8").send(k(o.playwrightFile,"utf8"))}catch(t){e.status(500).type("text/plain").send(t.message)}}),g.get("/api/sessions/:sessionId/codegen/selenium",(s,e)=>{try{const{sessionId:t}=s.params,n=R(t)||a(O(),t);if(!l(n))return e.status(404).type("text/plain").send("Session not found");const o=oe(n,S);if(!o?.seleniumFile||!l(o.seleniumFile))return e.status(404).type("text/plain").send("No Selenium artifact");e.type("text/plain; charset=utf-8").send(k(o.seleniumFile,"utf8"))}catch(t){e.status(500).type("text/plain").send(t.message)}}),g.post("/api/init",async(s,e)=>{const{agentType:t,apiKey:n}=s.body;try{const o=`zibby init --agent ${t} --headed`,i=Q(o,[],{cwd:S,env:{...process.env,CURSOR_API_KEY:t==="cursor"?n:process.env.CURSOR_API_KEY,ANTHROPIC_API_KEY:t==="claude"?n:process.env.ANTHROPIC_API_KEY,OPENAI_API_KEY:t==="codex"?n:process.env.OPENAI_API_KEY,GEMINI_API_KEY:t==="gemini"?n:process.env.GEMINI_API_KEY,GOOGLE_API_KEY:t==="gemini"?n:process.env.GOOGLE_API_KEY,CI:"true",ZIBBY_CI:"true"},shell:!0});let u="",r="";i.stdout.on("data",p=>{u+=p.toString()}),i.stderr.on("data",p=>{r+=p.toString()});let c=!1;const d=(p,m)=>{if(!c)if(c=!0,p===0)e.json({success:!0,stdout:u});else{const N=m||r||"zibby init failed";e.status(500).json({success:!1,error:N,stdout:u})}};i.on("close",p=>d(p)),i.on("error",p=>d(1,p.message))}catch(o){e.status(500).json({success:!1,error:o.message})}});const ne=(s,e)=>{const t={type:s,message:e,time:new Date().toLocaleTimeString()};E.clients.forEach(n=>{if(n.listenType==="console")try{n.send(JSON.stringify(t))}catch{}})},We=console.log,Je=console.error,Me=console.warn,re=s=>s.map(e=>{if(e==null)return String(e);if(typeof e=="object")try{return rt(e,{depth:4,colors:!1,breakLength:100})}catch{return String(e)}return String(e)}).join(" ");console.log=(...s)=>{We(...s),ne("info",re(s))},console.error=(...s)=>{Je(...s),ne("error",re(s))},console.warn=(...s)=>{Me(...s),ne("warn",re(s))},E.on("connection",(s,e)=>{const t=e.url;if(t.includes("/console"))s.listenType="console",console.log("[WebSocket] Console listener connected"),s.send(JSON.stringify({type:"success",message:"\u{1F3AD} Connected to Studio server logs",time:new Date().toLocaleTimeString()}));else{let n=t.split("/").pop()||"";try{n=decodeURIComponent(n)}catch{}s.sessionId=n,s.listenType="session",console.log(`[WebSocket] Session listener connected: ${n}`)}s.on("close",()=>{s.listenType==="console"?console.log("[WebSocket] Console listener disconnected"):console.log(`[WebSocket] Session listener disconnected: ${s.sessionId}`)})}),g.get("/api/workflow/graph",async(s,e)=>{try{const t=a(S,".zibby","graph.mjs");if(!l(t))return console.warn("[Workflow Graph API] Graph file not found at",t),e.json({graph:null,error:"No graph.mjs found in .zibby folder"});const n=await import(`${ct(t).href}?t=${Date.now()}`),o=n.default||Object.values(n)[0];if(!o||typeof o!="function")return console.error("[Workflow Graph API] Invalid graph module, got:",typeof o),e.json({graph:null,error:"Invalid graph module"});const u=new o().buildGraph(),r=u.toJSON?u.toJSON():u.serialize();console.log(`[Workflow Graph API] graph OK nodes=${r.nodes?.length??0} edges=${r.edges?.length??0} (${t})`),e.json({graph:r})}catch(t){console.error("[Workflow Graph API] Failed to load workflow graph:",t),e.status(500).json({graph:null,error:t.message})}}),K||g.get("*",(s,e)=>{const t=a(le,"../../studio-dist/index.html");l(t)?e.sendFile(t):e.status(404).send("Studio not built. Run: cd studio && npm run build:web")});const Ke=()=>{K?(console.log(`
10
10
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
11
11
  \u2551 \u{1F525} Zibby Studio (DEV MODE) \u2551
12
12
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
@@ -19,7 +19,7 @@ ${P.stderr.slice(-J)}`),U.set(o,P),h().forEach(v=>{try{v.send(JSON.stringify({ty
19
19
  \u26A1 Hot reload enabled
20
20
  ${W?"\u{1F680} Vite auto-started":"\u2705 Using existing Vite server"}
21
21
  Press Ctrl+C to stop
22
- `),console.log("[Studio] POST /api/run returns 202 immediately; poll GET /api/run/result/:sessionId (avoids gateway timeouts on long runs)."),j.open!==!1&&(j.web?_e(`http://localhost:${G}`):de().catch(s=>{console.error(`[Studio] Failed to launch desktop app: ${s.message}`),console.error("[Studio] Run with --web to open browser instead.")}))):(console.log(`
22
+ `),console.log("[Studio] POST /api/run returns 202 immediately; poll GET /api/run/result/:sessionId (avoids gateway timeouts on long runs)."),j.open!==!1&&(j.web?Ae(`http://localhost:${G}`):de().catch(s=>{console.error(`[Studio] Failed to launch desktop app: ${s.message}`),console.error("[Studio] Run with --web to open browser instead.")}))):(console.log(`
23
23
  \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
24
24
  \u2551 \u{1F3AD} Zibby Studio Web \u2551
25
25
  \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
@@ -29,5 +29,5 @@ ${P.stderr.slice(-J)}`),U.set(o,P),h().forEach(v=>{try{v.send(JSON.stringify({ty
29
29
  Config: ${H()}
30
30
 
31
31
  Press Ctrl+C to stop
32
- `),console.log("[Studio] POST /api/run returns 202 immediately; poll GET /api/run/result/:sessionId (avoids gateway timeouts on long runs)."),j.open!==!1&&(j.web?_e(`http://localhost:${w}`):de().catch(s=>{console.error(`[Studio] Failed to launch desktop app: ${s.message}`),console.error("[Studio] Run with --web to open browser instead.")})))};let me=!1;function ge(){const s=async e=>{if(I.removeListener("error",s),e.code==="EADDRINUSE"&&!me){me=!0,console.log(`[Studio] Port ${w} still busy \u2014 forcing cleanup and retrying...`),await Ce(w),ge();return}console.error("[Studio] Server error:",e.message),process.exit(1)};I.once("error",s),I.listen(w,()=>{I.removeListener("error",s),I.on("error",e=>{console.error("[Studio] Runtime error:",e.message)}),Ke()})}await Ce(w),ge();const he=()=>{console.log(`
32
+ `),console.log("[Studio] POST /api/run returns 202 immediately; poll GET /api/run/result/:sessionId (avoids gateway timeouts on long runs)."),j.open!==!1&&(j.web?Ae(`http://localhost:${w}`):de().catch(s=>{console.error(`[Studio] Failed to launch desktop app: ${s.message}`),console.error("[Studio] Run with --web to open browser instead.")})))};let me=!1;function ge(){const s=async e=>{if(I.removeListener("error",s),e.code==="EADDRINUSE"&&!me){me=!0,console.log(`[Studio] Port ${w} still busy \u2014 forcing cleanup and retrying...`),await Ce(w),ge();return}console.error("[Studio] Server error:",e.message),process.exit(1)};I.once("error",s),I.listen(w,()=>{I.removeListener("error",s),I.on("error",e=>{console.error("[Studio] Runtime error:",e.message)}),Ke()})}await Ce(w),ge();const he=()=>{console.log(`
33
33
  Stopping Studio...`);const s=O();for(const e of C.keys()){const t=x(a(s,e));try{B(t,{status:"interrupted",runSource:"studio",activeNode:null,activeStageIndex:null,exitReason:"studio-shutdown"})}catch{}try{D(a(s,e))}catch{}}W&&(W.kill(),console.log("Stopped Vite dev server")),C.forEach(e=>e.kill()),I.close(()=>{console.log("Studio stopped"),process.exit(0)})};process.on("SIGINT",he),process.on("SIGTERM",he)}export{Rt as studioCommand};
@@ -1,7 +1,7 @@
1
- import e from"os";import i from"path";import{existsSync as u,readFileSync as g,createWriteStream as h,mkdirSync as y,rmSync as S}from"fs";import{extract as b}from"tar";import x from"adm-zip";import a from"chalk";const $=process.env.ZIBBY_STUDIO_CDN||"https://cdn.zibby.app/studio/latest",r=i.join(e.homedir(),".zibby","studio");function v(){const t=e.platform(),o=e.arch();if(t==="darwin")return`darwin-${o}.tar.gz`;if(t==="win32")return`win32-${o}.zip`;throw new Error(`Unsupported platform: ${t} ${o}`)}function z(){const t=v();return`${$}/${t}`}async function A(){const t=z(),o=i.basename(t);console.log(a.cyan(`
2
- \u{1F4E6} Downloading Zibby Studio...`)),console.log(a.gray(` ${t}
3
- `));try{const n=await fetch(t);if(!n.ok)throw new Error(`Download failed: ${n.status} ${n.statusText}`);const s=parseInt(n.headers.get("content-length")||"0",10),c=i.join(e.tmpdir(),o);let l=0;const d=h(c);for await(const p of n.body)if(l+=p.length,d.write(p),s>0){const f=(l/s*100).toFixed(1),m=(l/1024/1024).toFixed(1),w=(s/1024/1024).toFixed(1);process.stdout.write(`\r \u{1F4E5} ${f}% (${m}MB / ${w}MB)`)}return d.end(),console.log(`
4
- `),u(r)&&(console.log(a.gray(" \u{1F5D1}\uFE0F Removing old version...")),S(r,{recursive:!0,force:!0})),y(r,{recursive:!0}),console.log(a.cyan(" \u{1F4C2} Extracting...")),c.endsWith(".tar.gz")?await D(c,r):c.endsWith(".zip")&&await I(c,r),console.log(a.green(` \u2705 Zibby Studio installed!
5
- `)),!0}catch(n){throw console.log(a.red(`
1
+ import s from"os";import a from"path";import{existsSync as p,readFileSync as y,readdirSync as S,createWriteStream as h,mkdirSync as x,rmSync as I}from"fs";import{createInterface as v}from"readline";import i from"chalk";const Z=process.env.ZIBBY_STUDIO_CDN||"https://dl.zibby.app",e=a.join(s.homedir(),".zibby","studio");function w(){const o=s.platform(),t=s.arch();if(o==="darwin"&&t==="arm64")return{archive:"Zibby Studio-mac-arm64.zip",label:"macOS (Apple Silicon)"};if(o==="darwin")return{archive:"Zibby Studio-mac-x64.zip",label:"macOS (Intel)"};if(o==="win32"&&t==="arm64")return{archive:"Zibby Studio-win-arm64.zip",label:"Windows (ARM)"};if(o==="win32")return{archive:"Zibby Studio-win-x64.zip",label:"Windows (x64)"};if(o==="linux"&&t==="arm64")return{archive:"Zibby Studio-arm64.AppImage",label:"Linux (ARM64)"};if(o==="linux")return{archive:"Zibby Studio-1.0.0.AppImage",label:"Linux (x64)"};throw new Error(`Unsupported platform: ${o} ${t}`)}function b(){const{archive:o}=w();return`${Z}/download/latest/${encodeURIComponent(o)}`}function $(o){const t=v({input:process.stdin,output:process.stdout});return new Promise(n=>{t.question(o,c=>{t.close(),n(c.trim().toLowerCase())})})}async function R(){const{label:o}=w(),t=b();console.log(""),console.log(i.cyan(" Zibby Studio is not installed.")),console.log(i.gray(` Platform: ${o}`)),console.log(i.gray(` Install location: ${e}`)),console.log(i.gray(` Download: ${t}`)),console.log("");const n=await $(i.white(" Download and install Zibby Studio? (Y/n): "));return n&&n!=="y"&&n!=="yes"?(console.log(""),console.log(i.yellow(" Skipped. You can run with --web to use Studio in the browser instead.")),console.log(""),!1):(await A(),!0)}async function A(){const o=b(),t=decodeURIComponent(a.basename(o));console.log(i.cyan(`
2
+ \u{1F4E6} Downloading Zibby Studio...`)),console.log(i.gray(` ${decodeURIComponent(o)}
3
+ `));try{const n=await fetch(o);if(!n.ok)throw new Error(`Download failed: ${n.status} ${n.statusText}`);const c=parseInt(n.headers.get("content-length")||"0",10),l=a.join(s.tmpdir(),t);let m=0;const f=h(l);for await(const r of n.body)if(m+=r.length,f.write(r),c>0){const u=(m/c*100).toFixed(1),d=(m/1024/1024).toFixed(1),g=(c/1024/1024).toFixed(1);process.stdout.write(`\r \u{1F4E5} ${u}% (${d}MB / ${g}MB)`)}if(f.end(),console.log(`
4
+ `),p(e)&&(console.log(i.gray(" \u{1F5D1}\uFE0F Removing old version...")),I(e,{recursive:!0,force:!0})),x(e,{recursive:!0}),console.log(i.cyan(" \u{1F4C2} Extracting...")),l.endsWith(".zip")){const r=(await import("adm-zip")).default;new r(l).extractAllTo(e,!0)}else if(l.endsWith(".tar.gz")){const{extract:r}=await import("tar");await r({file:l,cwd:e,strip:1})}else if(l.endsWith(".AppImage")){const{copyFileSync:r,chmodSync:u}=await import("fs"),d=a.join(e,t);r(l,d),u(d,493)}return console.log(i.green(` \u2705 Zibby Studio installed!
5
+ `)),!0}catch(n){throw console.log(i.red(`
6
6
  \u274C Installation failed: ${n.message}
7
- `)),n}}async function D(t,o){await b({file:t,cwd:o,strip:1})}async function I(t,o){new x(t).extractAllTo(o,!0)}function _(){const t=Z();return!!(t&&u(t))}function Z(){return e.platform()==="darwin"?i.join(r,"Zibby Studio.app"):e.platform()==="win32"?i.join(r,"Zibby Studio.exe"):null}function E(){const t=i.join(r,"version.txt");return u(t)?g(t,"utf-8").trim():"unknown"}export{Z as getStudioAppPath,E as getStudioVersion,A as installStudio,_ as isStudioInstalled};
7
+ `)),n}}function W(){const o=z();return!!(o&&p(o))}function z(){if(s.platform()==="darwin")return a.join(e,"Zibby Studio.app");if(s.platform()==="win32")return a.join(e,"Zibby Studio.exe");if(s.platform()==="linux"){if(!p(e))return null;const o=S(e).find(t=>t.endsWith(".AppImage"));return o?a.join(e,o):null}return null}function B(){const o=a.join(e,"version.txt");return p(o)?y(o,"utf-8").trim():"unknown"}export{z as getStudioAppPath,B as getStudioVersion,A as installStudio,W as isStudioInstalled,R as promptAndInstallStudio};
@@ -1 +1 @@
1
- import{spawn as i}from"child_process";import s from"os";import{getStudioAppPath as e}from"./studio-installer.js";function f(r={}){const o=e();if(!o)throw new Error("Studio not installed");const t=[];r.port&&t.push("--port",String(r.port)),r.sessionsDir&&t.push("--sessions",r.sessionsDir),s.platform()==="darwin"?i("open",[o,"--args",...t],{detached:!0,stdio:"ignore"}).unref():s.platform()==="win32"&&i(o,t,{detached:!0,stdio:"ignore"}).unref()}export{f as launchStudio};
1
+ import{spawn as o}from"child_process";import i from"os";import{getStudioAppPath as s}from"./studio-installer.js";function f(t={}){const e=s();if(!e)throw new Error("Studio not installed");const r=[];t.port&&r.push("--port",String(t.port)),t.sessionsDir&&r.push("--sessions",t.sessionsDir),i.platform()==="darwin"?o("open",[e,"--args",...r],{detached:!0,stdio:"ignore"}).unref():i.platform()==="win32"?o(e,r,{detached:!0,stdio:"ignore"}).unref():o(e,r,{detached:!0,stdio:"ignore"}).unref()}export{f as launchStudio};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/cli",
3
- "version": "0.1.27",
3
+ "version": "0.1.29",
4
4
  "description": "Zibby CLI - Test automation generator and runner",
5
5
  "type": "module",
6
6
  "bin": {
@@ -51,6 +51,7 @@
51
51
  "ws": "^8.20.0"
52
52
  },
53
53
  "files": [
54
+ "dist/bin/",
54
55
  "dist/",
55
56
  "README.md",
56
57
  "LICENSE"
@@ -58,6 +59,10 @@
58
59
  "engines": {
59
60
  "node": ">=18.0.0"
60
61
  },
62
+ "overrides": {
63
+ "axios": "^1.15.0",
64
+ "undici": "^7.24.0"
65
+ },
61
66
  "devDependencies": {
62
67
  "esbuild": "^0.28.0",
63
68
  "vitest": "^4.1.4"