@zibby/cli 0.1.31 → 0.1.33
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/commands/studio.js
CHANGED
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
`)){if(!
|
|
4
|
-
`).filter(Boolean))].map(
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
import It from"express";import{createServer as Jt}from"http";import{WebSocketServer as Mt}from"ws";import{spawn as st,execSync as Y}from"child_process";import{readFileSync as _,existsSync as u,readdirSync as $,statSync as L,createReadStream as Kt,createWriteStream as Yt,mkdirSync as bt,openSync as Ht,readSync as Zt,closeSync as Vt,writeFileSync as H,unlinkSync as qt}from"fs";import{join as a,resolve as O,dirname as jt,basename as Qt}from"path";import{homedir as Xt}from"os";import{inspect as te}from"util";import{fileURLToPath as ee,pathToFileURL as se}from"url";import ne from"dotenv";import{promptAndInstallStudio as oe,isStudioInstalled as re}from"../utils/studio-installer.js";import{launchStudio as ie}from"../utils/studio-launcher.js";import{mergeSessionRunState as B,listRunningSessionStatesFromSessionsRoot as ce}from"@zibby/core/utils/run-state-session.js";import{liveRunsFromSessionStateRows as ae}from"@zibby/core/utils/session-state-live-runs.js";import{findLatestLiveFrameFileSync as Pt,readLatestLiveFramePayloadSync as vt}from"@zibby/core/utils/live-frame-discovery.js";import{getApiUrl as wt}from"../config/environments.js";const le=ee(import.meta.url),ue=jt(le);function Tt(A){const g=a(A,"video.webm");if(u(g))return g;const y=a(A,"execute_live");if(!u(y))return null;try{const k=$(y).filter(I=>I.endsWith(".webm"));return k.length===0?null:k.map(I=>{const v=a(y,I);try{return{p:v,mtime:L(v).mtimeMs}}catch{return{p:v,mtime:0}}}).sort((I,v)=>v.mtime-I.mtime)[0].p}catch{return null}}function Nt(A){if(process.platform==="win32")try{const g=Y("netstat -ano",{encoding:"utf8"}),y=new Set;for(const k of g.split(`
|
|
3
|
+
`)){if(!k.includes("LISTENING"))continue;const E=k.trim().split(/\s+/);if(!(E[1]||"").endsWith(`:${A}`))continue;const v=E[E.length-1];/^\d+$/.test(v)&&y.add(parseInt(v,10))}return[...y].filter(k=>k!==process.pid)}catch{return[]}try{const g=Y(`lsof -tiTCP:${A} -sTCP:LISTEN`,{encoding:"utf8"}).trim();return[...new Set(g.split(`
|
|
4
|
+
`).filter(Boolean))].map(y=>parseInt(y,10)).filter(y=>!Number.isNaN(y)&&y!==process.pid)}catch{return[]}}async function At(A){let g=Nt(A);if(g.length!==0){console.log(`[Studio] Port ${A} in use \u2014 stopping previous Studio listener(s): ${g.join(", ")}`);for(const y of g)try{process.kill(y,"SIGTERM")}catch{}await new Promise(y=>setTimeout(y,450)),g=Nt(A);for(const y of g)try{process.kill(y,"SIGKILL")}catch{}await new Promise(y=>setTimeout(y,200))}}async function ke(A={}){const g=It(),y=Jt(g),k=new Mt({server:y}),E=A.port||3847,I=process.cwd(),v=Xt();process.env.DOTENV_CONFIG_QUIET="true";const _t=process.env.NODE_ENV||"development";[O(I,".env.local"),O(I,`.env.${_t}`),O(I,".env")].forEach(s=>{u(s)&&ne.config({path:s,override:!1})});const R=new Map,D=new Map;let kt=null;const G=96e3,nt="studio-cli.log",U="studio-run.json",Et=".zibby-studio-stop",Z=512*1024;g.use(It.json()),g.get("/api/session-run-states",(s,t)=>{try{let e=x();try{const r=new URL(s.url,"http://zibby.studio").searchParams.get("sessionsRoot");if(r&&String(r).trim()){const i=O(decodeURIComponent(String(r).trim()));u(i)&&L(i).isDirectory()&&(e=i)}}catch{}const o=ce(e),{liveIdList:n,progressByKey:c}=ae(o);t.json({rows:o,liveIdList:n,progressByKey:c,sessionsRoot:e,unavailable:!1})}catch(e){t.status(500).json({error:e.message,rows:[],liveIdList:[],progressByKey:{},unavailable:!0})}}),g.use((s,t,e)=>(t.header("Access-Control-Allow-Origin","*"),t.header("Access-Control-Allow-Methods","GET, POST, PUT, DELETE"),t.header("Access-Control-Allow-Headers","Content-Type"),e()));const V=a(ue,"../../../../studio"),ot=u(a(V,"package.json"));function Ot(){const s=a(V,"node_modules",".bin",process.platform==="win32"?"electron.cmd":"electron"),t=u(s),n=st(t?s:"npx",t?["."]:["electron","."],{cwd:V,detached:!0,stdio:"ignore",shell:!1,env:{...process.env,ZIBBY_STUDIO_PROJECT_ROOT:I,ZIBBY_STUDIO_API_BASE:`http://localhost:${E}/api`}});n.unref(),kt=n}async function xt(){if(ot){Ot();return}if(!re()&&!await oe()){y.close(),process.exit(0);return}console.log(`
|
|
5
|
+
\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\u2557
|
|
6
|
+
\u2551 Zibby Studio \u2551
|
|
7
|
+
\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\u255D
|
|
8
|
+
`),ie({projectRoot:I}),setTimeout(()=>{y.close(),process.exit(0)},1e3)}function rt(){const s=a(I,".zibby.config.mjs");return u(s)?s:a(v,".zibby.config.mjs")}function x(){rt();const s=a(I,".zibby","output","sessions");return u(s)?s:a(v,".zibby","output","sessions")}function q(s,t){let e=null,o=null,n=null;const c=a(s,"codegen");if(u(c)){const r=a(c,"test.spec.ts"),i=a(c,"generated-test.spec.js");e=u(r)?r:u(i)?i:null;const d=a(c,"test.selenium.py"),p=a(c,"generated-test-selenium.js");o=u(d)?d:u(p)?p:null;const m=a(c,"trace.zip");n=u(m)?m:null}const l=a(s,"generate_script","result.json");if(!e&&u(l))try{const i=JSON.parse(_(l,"utf8"))?.scriptPath;if(typeof i=="string"&&i.trim()){const d=i.trim(),p=d.startsWith("/")||process.platform==="win32"&&/^[A-Za-z]:[\\/]/.test(d)?d:a(t,d);u(p)&&(e=p)}}catch(r){console.warn("[Studio API] generate_script result.json:",r.message)}return!e&&!o&&!n?null:{playwrightFile:e,seleniumFile:o,tracePath:n}}function Rt(s,t,e){try{H(a(s,U),JSON.stringify({sessionId:t,pid:e??null,startedAt:Date.now()},null,2),"utf8")}catch(o){console.error("[Studio API] writeStudioRunMeta:",o.message)}}function z(s){try{const t=a(s,U);u(t)&&qt(t)}catch{}}function Ct(s){const t=Number(s);if(!Number.isFinite(t)||t<=0)return[];try{const e=Y(`pgrep -P ${t}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:524288}).trim();return e?e.split(/\n/).map(o=>parseInt(o.trim(),10)).filter(o=>Number.isFinite(o)&&o>0):[]}catch{return[]}}function W(s,t){const e=Number(s);if(!Number.isFinite(e)||e<=0)return;const o=new Set;function n(c){if(!o.has(c)){o.add(c);for(const l of Ct(c))n(l);try{process.kill(c,t)}catch{}}}n(e)}function it(s){const t=Number(s);if(!(!Number.isFinite(t)||t<=0))try{Y(`taskkill /PID ${t} /T /F`,{stdio:"ignore",windowsHide:!0})}catch{}}function ct(s){if(!Number.isFinite(s)||s<=0)return;if(process.platform==="win32"){it(s);return}W(s,"SIGTERM");const t=setTimeout(()=>{W(s,"SIGKILL")},800);typeof t.unref=="function"&&t.unref()}function Ft(s,t){const o=C(s)||a(x(),s);try{bt(o,{recursive:!0}),H(a(o,Et),JSON.stringify({requestedAt:Date.now()}),"utf8")}catch(r){console.error("[Studio API] write studio stop request:",r.message)}const n=R.get(s);if(n){const r=n.pid;if(process.platform==="win32")it(r);else{W(r,"SIGTERM");const i=setTimeout(()=>{W(r,"SIGKILL")},800);typeof i.unref=="function"&&i.unref()}return R.delete(s),z(o),!0}const c=a(o,U);if(u(c)){let r=null;try{const i=JSON.parse(_(c,"utf8"));r=Number(i.pid)}catch(i){console.error("[Studio API] studio-run.json read:",i.message)}if(z(o),Number.isFinite(r)&&r>0)return ct(r),!0}const l=Number(t);return Number.isFinite(l)&&l>0?(ct(l),z(o),!0):!1}function J(s){try{const t=s?.query?.sessionsRoot;if(typeof t!="string")return"";const e=t.trim();if(!e)return"";const o=decodeURIComponent(e),n=O(o);return u(n)?n:""}catch{return""}}function C(s,t=""){const e=typeof s=="string"?decodeURIComponent(s).trim():String(s||"").trim();if(!e)return null;const o=[e],n=e.split("_")[0]?.trim()||"";n&&n!==e&&o.push(n);const c=new Set,l=[],r=new Set,i=[],d=h=>{if(!h)return;const f=O(h);r.has(f)||(r.add(f),i.push(f))},p=h=>{if(h){d(h);for(const f of o){const S=a(h,f);c.has(S)||(c.add(S),l.push(S))}}};p(t),p(x()),p(a(v,".zibby","output","sessions"));let m;try{m=$(I)}catch{m=[]}for(const h of m){if(h.startsWith("."))continue;const f=a(I,h);try{if(!L(f).isDirectory())continue}catch{continue}p(a(f,".zibby","output","sessions"));let S;try{S=$(f,{withFileTypes:!0})}catch{S=[]}for(const T of S)T?.isDirectory?.()&&String(T.name||"").startsWith(".zibby")&&p(a(f,T.name,"output","sessions"))}const w=l.filter(h=>u(h));if(w.length===0)for(const h of i){if(!u(h))continue;let f;try{f=$(h,{withFileTypes:!0})}catch{f=[]}for(const S of f){if(!S?.isDirectory?.())continue;const T=String(S.name||"");if(T===e||T===n||T.startsWith(`${e}_`)||n&&T.startsWith(`${n}_`)){const F=a(h,T);if(c.has(F))continue;c.add(F),w.push(F)}}}if(w.length===0)return null;if(w.length===1)return w[0];const M=h=>{let f=0;u(a(h,"execute_live"))&&(f+=20),Pt(h)&&(f+=15),u(a(h,"execute_live","events.json"))&&(f+=8),u(a(h,"execute_live","result.json"))&&(f+=6),u(a(h,U))&&(f+=4),u(a(h,".session-info.json"))&&(f+=2);try{const S=a(h,"execute_live");u(S)&&(f+=Math.min(L(S).mtimeMs/1e12,3))}catch{}return f};return w.sort((h,f)=>M(f)-M(h)),w[0]}function Lt(s,t){try{const e=typeof s=="string"?decodeURIComponent(s).trim():String(s||"").trim();if(!e||!t)return null;const o=(e.split("_")[0]||"").trim(),n=jt(t);if(!o||!n||!u(n))return null;let c=[];try{c=$(n,{withFileTypes:!0})}catch{c=[]}let l=null;for(const r of c){if(!r?.isDirectory?.())continue;const i=String(r.name||"");if(!(i===e||i===o||i.startsWith(`${e}_`)||i.startsWith(`${o}_`)))continue;const d=a(n,i),p=vt(d);p&&(!l||Number(p.mtime||0)>Number(l.mtime||0))&&(l=p)}return l}catch{return null}}g.get("/api/config/check",(s,t)=>{const e=rt();t.json({exists:u(e),path:e,isProjectLevel:e.startsWith(I)})}),g.get("/api/projects",(s,t)=>{try{const e=a(v,".zibby","config.json");if(!u(e))return t.json({projects:[]});const o=JSON.parse(_(e,"utf8")),n=typeof o.sessionToken=="string"&&o.sessionToken.trim()!==""?o.sessionToken.trim():"",c=Array.isArray(o.projects)?o.projects:[];if(!n)return t.json({projects:c});const l=String(wt()).replace(/\/$/,"");fetch(`${l}/projects`,{headers:{Authorization:`Bearer ${n}`}}).then(r=>r.json().catch(()=>({})).then(i=>({ok:r.ok,status:r.status,body:i}))).then(({ok:r,body:i})=>{if(!r)return t.json({projects:c});const p=(Array.isArray(i?.projects)?i.projects:[]).map(m=>({name:m?.name,projectId:m?.projectId,apiToken:m?.apiToken||null,createdAt:m?.createdAt||null,updatedAt:m?.updatedAt||null}));o.projects=p;try{H(e,`${JSON.stringify(o,null,2)}
|
|
9
|
+
`,"utf8")}catch{}return t.json({projects:p})}).catch(()=>t.json({projects:c}))}catch(e){t.status(500).json({error:e.message})}}),g.post("/api/projects/create",async(s,t)=>{try{const e=typeof s.body?.name=="string"?s.body.name.trim():"";if(!e)return t.status(400).json({error:"Project name is required"});const o=a(v,".zibby","config.json");if(!u(o))return t.status(401).json({error:"Not logged in"});const n=JSON.parse(_(o,"utf8")),c=typeof n.sessionToken=="string"&&n.sessionToken.trim()!==""?n.sessionToken.trim():"";if(!c)return t.status(401).json({error:"Not logged in"});const l=String(wt()).replace(/\/$/,""),r=await fetch(`${l}/projects`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${c}`},body:JSON.stringify({name:e})}),i=await r.json().catch(()=>({}));if(!r.ok)return t.status(r.status).json({error:i.error||i.message||`HTTP ${r.status}`});const d=i?.project&&typeof i.project=="object"?i.project:null;if(d?.projectId){const m=(Array.isArray(n.projects)?n.projects:[]).filter(w=>String(w?.projectId||"")!==String(d.projectId));m.push({name:d.name||e,projectId:d.projectId,apiToken:d.apiToken||i?.apiToken||null,createdAt:d.createdAt||null,updatedAt:d.updatedAt||null}),n.projects=m;try{H(o,`${JSON.stringify(n,null,2)}
|
|
10
|
+
`,"utf8")}catch{}}return t.json(i)}catch(e){return t.status(500).json({error:e.message||String(e)})}});function $t(s,t){const e=Array.isArray(t)?t.filter(d=>typeof d=="string"):[],o=process.argv[1]?O(process.argv[1]):"",n=o?Qt(o):"";let c;try{c=!!o&&u(o)&&L(o).isFile()&&!/\.(cmd|ps1|bat)$/i.test(o)&&(/\.(m|c)?js$/i.test(o)||n==="zibby")}catch{c=!1}if(c){const d=[o,"test",s,...e],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 l=s.replace(/"/g,'\\"'),r=e.join(" "),i=`zibby test "${l}" ${r}`.trim();return{useShell:!0,cmd:i,args:[],display:i}}g.get("/api/run/result/:sessionId",(s,t)=>{const{sessionId:e}=s.params,o=D.get(e);if(!o)return t.status(404).json({pending:!0,error:"unknown_session"});t.json(o)}),g.post("/api/run",async(s,t)=>{const{task:e,args:o=[],sessionId:n,studioTestCaseId:c}=s.body;if(!e||!n)return t.status(400).json({success:!1,error:"task and sessionId are required"});const l=x(),r=a(l,n),i=O(r),d=c!=null&&String(c).trim()!==""?String(c).trim():String(n);D.set(n,{pending:!0});let p=!1,m=null;const w=()=>{if(m&&!m.destroyed)try{m.end()}catch{}m=null},M=(f,S)=>{D.delete(n),w(),p||t.status(f).json(S)},h=()=>{const f=[];return k.clients.forEach(S=>{S.sessionId===n&&f.push(S)}),f};try{const f=$t(e,o);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]),bt(r,{recursive:!0}),m=Yt(a(r,nt),{flags:"w"}),B(i,{sessionId:String(n),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:I,outputBase:".zibby/output",sessionPathAbs:i});const S=st(f.cmd,f.args,{cwd:I,shell:f.useShell,env:{...process.env,ZIBBY_SESSION_ID:n,ZIBBY_SESSION_PATH:O(r)}});R.set(n,S),Rt(r,n,S.pid),B(i,{sessionId:String(n),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:I,outputBase:".zibby/output",sessionPathAbs:i,pid:S.pid??null});let T="",F="",dt=!1,ft=0;const pt=setInterval(()=>{try{const b=Pt(r);if(!b||b.mtime<=ft)return;ft=b.mtime;const j=_(b.p).toString("base64"),P=b.p.toLowerCase().endsWith(".png")?"image/png":"image/jpeg";h().forEach(K=>{try{K.send(JSON.stringify({type:"video-frame",sessionId:n,frame:j,mime:P}))}catch{}})}catch{}},320),mt=b=>{if(dt)return;dt=!0,w();const j={pending:!1,...b};typeof j.stdout=="string"&&j.stdout.length>G&&(j.stdout=`\u2026(truncated)
|
|
7
11
|
${j.stdout.slice(-G)}`),typeof j.stderr=="string"&&j.stderr.length>G&&(j.stderr=`\u2026(truncated)
|
|
8
|
-
${j.stderr.slice(-G)}`),D.set(n,j),h().forEach(P=>{try{P.send(JSON.stringify({type:"run-complete",sessionId:n,result:b}))}catch{}})};
|
|
9
|
-
\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\
|
|
10
|
-
\u2551
|
|
11
|
-
\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\
|
|
12
|
+
${j.stderr.slice(-G)}`),D.set(n,j),h().forEach(P=>{try{P.send(JSON.stringify({type:"run-complete",sessionId:n,result:b}))}catch{}})};S.stdout.on("data",b=>{const j=b.toString();if(T+=j,m&&!m.destroyed)try{m.write(j)}catch(P){console.error("[Studio API] studio-cli.log write failed:",P.message)}h().forEach(P=>{try{P.send(JSON.stringify({type:"stdout",data:j}))}catch{}})}),S.stderr.on("data",b=>{const j=b.toString();if(F+=j,m&&!m.destroyed)try{m.write(`[stderr] ${j}`)}catch(P){console.error("[Studio API] studio-cli.log write failed:",P.message)}h().forEach(P=>{try{P.send(JSON.stringify({type:"stderr",data:j}))}catch{}})}),S.on("error",b=>{clearInterval(pt),R.delete(n),B(i,{sessionId:String(n),studioTestCaseId:d,status:"failed",runSource:"studio",activeNode:null,activeStageIndex:null,errorMessage:b.message,cwd:I,outputBase:".zibby/output",sessionPathAbs:i}),console.error("[Studio API] Spawn error:",b.message),mt({heldUntilExit:!0,success:!1,exitCode:null,error:b.message,stderr:F,stdout:T,runName:null,videoPath:null,eventsPath:null,codegenFiles:null,metadata:{sessionId:n,task:e}})}),S.on("close",b=>{clearInterval(pt),R.delete(n),B(i,{sessionId:String(n),studioTestCaseId:d,status:b===0?"completed":b===130||b===143?"interrupted":"failed",runSource:"studio",activeNode:null,activeStageIndex:null,exitCode:b,cwd:I,outputBase:".zibby/output",sessionPathAbs:i}),z(r),h().forEach(N=>{try{N.send(JSON.stringify({type:"exit",code:b}))}catch{}});const j=b===0;let P=null;const K=a(r,".session-info.json"),gt=a(r,"result.json");if(u(K))try{const N=JSON.parse(_(K,"utf8"));P=N.name||N.task}catch(N){console.error("[Studio API] Failed to read session info:",N.message)}else if(u(gt))try{const N=JSON.parse(_(gt,"utf8"));P=N.name||N.task}catch(N){console.error("[Studio API] Failed to read result:",N.message)}const Ut=[/\[SessionRecorder\] Run name set: (.+)/,/Run name: (.+)/];if(!P)for(const N of Ut){const St=T.match(N);if(St){P=St[1].trim();break}}const Wt=q(r,I),tt=Tt(r),ht=a(r,"events.json"),yt=a(r,"execute_live","events.json"),et=u(ht)?ht:u(yt)?yt:null;mt({heldUntilExit:!0,success:j,exitCode:b,stdout:T,stderr:F,runName:P,videoPath:tt&&u(tt)?tt:null,eventsPath:et&&u(et)?et:null,codegenFiles:Wt,metadata:{sessionId:n,task:e}})}),p=!0,t.status(202).json({accepted:!0,sessionId:n,pid:S.pid!=null&&Number.isFinite(Number(S.pid))?Number(S.pid):null,pollPath:`/api/run/result/${n}`,message:"Run started. Poll GET /api/run/result/:sessionId until pending is false."})}catch(f){M(500,{success:!1,error:f.message})}}),g.post("/api/stop/:sessionId",(s,t)=>{const{sessionId:e}=s.params,o=s.body&&s.body.pid!=null?s.body.pid:null;if(Ft(e,o))return t.json({success:!0});t.status(404).json({success:!1,error:"Session not found"})}),g.get("/api/recordings",(s,t)=>{try{const e=x();if(!u(e))return t.json({recordings:[],path:e});const n=$(e).map(c=>{const l=a(e,c),r=a(l,".session-info.json"),i=a(l,"result.json");let d={};return u(r)?d=JSON.parse(_(r,"utf8")):u(i)&&(d=JSON.parse(_(i,"utf8"))),{sessionId:c,...d,path:l}});t.json({path:e,recordings:n})}catch(e){t.status(500).json({error:e.message})}}),g.get("/api/sessions/:sessionId/cli-log",(s,t)=>{try{const{sessionId:e}=s.params;let o=parseInt(String(s.query.offset||"0"),10);(Number.isNaN(o)||o<0)&&(o=0);const n=J(s),c=C(e,n),l=a(c||a(x(),e),nt);if(!u(l))return t.json({exists:!1,content:"",size:0,fromByte:0,nextOffset:0});const r=L(l).size;o>r&&(o=r);let i=o,d=r-i;d>Z&&(i=r-Z,d=Z);const p=Buffer.alloc(d),m=Ht(l,"r");try{Zt(m,p,0,d,i)}finally{Vt(m)}t.json({exists:!0,content:p.toString("utf8"),size:r,fromByte:i,nextOffset:r})}catch(e){t.status(500).json({error:e.message})}}),g.get("/api/sessions/:sessionId/events",(s,t)=>{try{const{sessionId:e}=s.params,o=J(s),n=C(e,o);if(!n)return t.json({events:[]});let c=a(n,"events.json");if(!u(c)){const r=a(n,"execute_live","events.json");if(u(r))c=r;else return t.json({events:[]})}const l=JSON.parse(_(c,"utf8"));t.json({events:l})}catch(e){t.status(500).json({error:e.message})}}),g.get("/api/sessions/:sessionId/live-preview",(s,t)=>{try{const{sessionId:e}=s.params,o=J(s),n=C(e,o);if(!n)return t.json({ok:!1,error:"session_not_found"});const l=vt(n)||Lt(e,n);if(!l)return t.json({ok:!1,error:"no_frame",sessionId:e,sessionPath:n});t.json({ok:!0,frame:l.base64,mime:l.mime,mtime:l.mtime,path:l.path})}catch(e){t.status(500).json({ok:!1,error:e.message})}}),g.get("/api/sessions/:sessionId/video",(s,t)=>{try{const{sessionId:e}=s.params,o=J(s),n=C(e,o);if(!n)return t.status(404).json({error:"Session not found"});const c=Tt(n);if(!c||!u(c))return t.status(404).json({error:"Video not found"});const l=L(c);t.writeHead(200,{"Content-Type":"video/webm","Content-Length":l.size}),Kt(c).pipe(t)}catch(e){t.status(500).json({error:e.message})}}),g.get("/api/sessions/:sessionId/codegen/playwright",(s,t)=>{try{const{sessionId:e}=s.params,o=C(e)||a(x(),e);if(!u(o))return t.status(404).type("text/plain").send("Session not found");const n=q(o,I);if(!n?.playwrightFile||!u(n.playwrightFile))return t.status(404).type("text/plain").send("No Playwright artifact");t.type("text/plain; charset=utf-8").send(_(n.playwrightFile,"utf8"))}catch(e){t.status(500).type("text/plain").send(e.message)}}),g.get("/api/sessions/:sessionId/codegen/selenium",(s,t)=>{try{const{sessionId:e}=s.params,o=C(e)||a(x(),e);if(!u(o))return t.status(404).type("text/plain").send("Session not found");const n=q(o,I);if(!n?.seleniumFile||!u(n.seleniumFile))return t.status(404).type("text/plain").send("No Selenium artifact");t.type("text/plain; charset=utf-8").send(_(n.seleniumFile,"utf8"))}catch(e){t.status(500).type("text/plain").send(e.message)}}),g.post("/api/init",async(s,t)=>{const{agentType:e,apiKey:o}=s.body;try{const n=`zibby init --agent ${e} --headed`,c=st(n,[],{cwd:I,env:{...process.env,CURSOR_API_KEY:e==="cursor"?o:process.env.CURSOR_API_KEY,ANTHROPIC_API_KEY:e==="claude"?o:process.env.ANTHROPIC_API_KEY,OPENAI_API_KEY:e==="codex"?o:process.env.OPENAI_API_KEY,GEMINI_API_KEY:e==="gemini"?o:process.env.GEMINI_API_KEY,GOOGLE_API_KEY:e==="gemini"?o:process.env.GOOGLE_API_KEY,CI:"true",ZIBBY_CI:"true"},shell:!0});let l="",r="";c.stdout.on("data",p=>{l+=p.toString()}),c.stderr.on("data",p=>{r+=p.toString()});let i=!1;const d=(p,m)=>{if(!i)if(i=!0,p===0)t.json({success:!0,stdout:l});else{const w=m||r||"zibby init failed";t.status(500).json({success:!1,error:w,stdout:l})}};c.on("close",p=>d(p)),c.on("error",p=>d(1,p.message))}catch(n){t.status(500).json({success:!1,error:n.message})}});const Q=(s,t)=>{const e={type:s,message:t,time:new Date().toLocaleTimeString()};k.clients.forEach(o=>{if(o.listenType==="console")try{o.send(JSON.stringify(e))}catch{}})},zt=console.log,Bt=console.error,Dt=console.warn,X=s=>s.map(t=>{if(t==null)return String(t);if(typeof t=="object")try{return te(t,{depth:4,colors:!1,breakLength:100})}catch{return String(t)}return String(t)}).join(" ");console.log=(...s)=>{zt(...s),Q("info",X(s))},console.error=(...s)=>{Bt(...s),Q("error",X(s))},console.warn=(...s)=>{Dt(...s),Q("warn",X(s))},k.on("connection",(s,t)=>{const e=t.url;if(e.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 o=e.split("/").pop()||"";try{o=decodeURIComponent(o)}catch{}s.sessionId=o,s.listenType="session",console.log(`[WebSocket] Session listener connected: ${o}`)}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,t)=>{try{const e=a(I,".zibby","graph.mjs");if(!u(e))return console.warn("[Workflow Graph API] Graph file not found at",e),t.json({graph:null,error:"No graph.mjs found in .zibby folder"});const o=await import(`${se(e).href}?t=${Date.now()}`),n=o.default||Object.values(o)[0];if(!n||typeof n!="function")return console.error("[Workflow Graph API] Invalid graph module, got:",typeof n),t.json({graph:null,error:"Invalid graph module"});const l=new n().buildGraph(),r=l.toJSON?l.toJSON():l.serialize();console.log(`[Workflow Graph API] graph OK nodes=${r.nodes?.length??0} edges=${r.edges?.length??0} (${e})`),t.json({graph:r})}catch(e){console.error("[Workflow Graph API] Failed to load workflow graph:",e),t.status(500).json({graph:null,error:e.message})}});const Gt=()=>{ot&&console.log(`
|
|
13
|
+
\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\u2557
|
|
14
|
+
\u2551 Zibby Studio \u2551
|
|
15
|
+
\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\u255D
|
|
12
16
|
|
|
13
|
-
API: http://localhost:${
|
|
17
|
+
API: http://localhost:${E}/api
|
|
14
18
|
Project: ${I}
|
|
15
|
-
Config: ${q()}
|
|
16
19
|
|
|
17
20
|
Press Ctrl+C to stop
|
|
18
|
-
`),
|
|
19
|
-
Stopping Studio...`);const s=
|
|
21
|
+
`),A.open!==!1&&xt().catch(s=>{console.error(`Failed to launch Zibby Studio: ${s.message}`)})};let at=!1;function lt(){const s=async t=>{if(y.removeListener("error",s),t.code==="EADDRINUSE"&&!at){at=!0,console.log(`[Studio] Port ${E} still busy \u2014 forcing cleanup and retrying...`),await At(E),lt();return}console.error("[Studio] Server error:",t.message),process.exit(1)};y.once("error",s),y.listen(E,()=>{y.removeListener("error",s),y.on("error",t=>{console.error("[Studio] Runtime error:",t.message)}),Gt()})}await At(E),lt();const ut=()=>{console.log(`
|
|
22
|
+
Stopping Studio...`);const s=x();for(const t of R.keys()){const e=O(a(s,t));try{B(e,{status:"interrupted",runSource:"studio",activeNode:null,activeStageIndex:null,exitReason:"studio-shutdown"})}catch{}try{z(a(s,t))}catch{}}R.forEach(t=>t.kill()),y.close(()=>{console.log("Studio stopped"),process.exit(0)})};process.on("SIGINT",ut),process.on("SIGTERM",ut)}export{ke as studioCommand};
|
package/dist/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
`))
|
|
4
|
-
`),p(e)
|
|
5
|
-
`)),!0}catch(
|
|
6
|
-
\u274C Installation failed: ${
|
|
7
|
-
`)),
|
|
1
|
+
import i from"os";import a from"path";import{existsSync as m,readFileSync as S,readdirSync as h,createWriteStream as g,mkdirSync as x,rmSync as I,unlinkSync as Z}from"fs";import{execFileSync as b}from"child_process";import{createInterface as v}from"readline";import l from"chalk";const A=process.env.ZIBBY_STUDIO_CDN||"https://dl.zibby.app",n=a.join(i.homedir(),".zibby","studio");function w(){const t=i.platform(),o=i.arch();if(t==="darwin"&&o==="arm64")return{archive:"Zibby Studio-mac-arm64.zip",label:"macOS (Apple Silicon)"};if(t==="darwin")return{archive:"Zibby Studio-mac-x64.zip",label:"macOS (Intel)"};if(t==="win32"&&o==="arm64")return{archive:"Zibby Studio-win-arm64.zip",label:"Windows (ARM)"};if(t==="win32")return{archive:"Zibby Studio-win-x64.zip",label:"Windows (x64)"};if(t==="linux"&&o==="arm64")return{archive:"Zibby Studio-arm64.AppImage",label:"Linux (ARM64)"};if(t==="linux")return{archive:"Zibby Studio-1.0.0.AppImage",label:"Linux (x64)"};throw new Error(`Unsupported platform: ${t} ${o}`)}function $(){const{archive:t}=w();return`${A}/download/latest/${encodeURIComponent(t)}`}function z(t){const o=v({input:process.stdin,output:process.stdout});return new Promise(e=>{o.question(t,u=>{o.close(),e(u.trim().toLowerCase())})})}async function C(){const{label:t}=w();console.log(""),console.log(l.cyan(" Zibby Studio is not installed.")),console.log(l.gray(` Platform: ${t}`)),console.log("");const o=await z(l.white(" Download and install Zibby Studio? (Y/n): "));return o&&o!=="y"&&o!=="yes"?(console.log(l.yellow(`
|
|
2
|
+
Skipped.
|
|
3
|
+
`)),!1):(await D(),!0)}async function D(){const t=$(),o=decodeURIComponent(a.basename(t));console.log(l.cyan(`
|
|
4
|
+
\u{1F4E6} Downloading Zibby Studio...`));try{const e=await fetch(t);if(!e.ok)throw new Error(`Download failed: ${e.status} ${e.statusText}`);const u=parseInt(e.headers.get("content-length")||"0",10),c=a.join(i.tmpdir(),o);let f=0;const p=g(c);for await(const r of e.body)if(f+=r.length,p.write(r),u>0){const s=(f/u*100).toFixed(1),d=(f/1024/1024).toFixed(1),y=(u/1024/1024).toFixed(1);process.stdout.write(`\r \u{1F4E5} ${s}% (${d}MB / ${y}MB)`)}if(await new Promise((r,s)=>{p.on("finish",r),p.on("error",s),p.end()}),console.log(""),m(n)&&I(n,{recursive:!0,force:!0}),x(n,{recursive:!0}),console.log(l.cyan(" \u{1F4C2} Installing...")),c.endsWith(".zip"))if(i.platform()==="darwin"||i.platform()==="linux"){if(b("unzip",["-oq",c,"-d",n]),i.platform()==="darwin")try{b("xattr",["-rd","com.apple.quarantine",n])}catch{}}else{const r=(await import("adm-zip")).default;new r(c).extractAllTo(n,!0)}else if(c.endsWith(".AppImage")){const{copyFileSync:r,chmodSync:s}=await import("fs"),d=a.join(n,o);r(c,d),s(d,493)}try{Z(c)}catch{}return console.log(l.green(` \u2705 Zibby Studio installed!
|
|
5
|
+
`)),!0}catch(e){throw console.log(l.red(`
|
|
6
|
+
\u274C Installation failed: ${e.message}
|
|
7
|
+
`)),e}}function M(){const t=k();return!!(t&&m(t))}function k(){if(i.platform()==="darwin")return a.join(n,"Zibby Studio.app");if(i.platform()==="win32")return a.join(n,"Zibby Studio.exe");if(i.platform()==="linux"){if(!m(n))return null;const t=h(n).find(o=>o.endsWith(".AppImage"));return t?a.join(n,t):null}return null}function O(){const t=a.join(n,"version.txt");return m(t)?S(t,"utf-8").trim():"unknown"}export{k as getStudioAppPath,O as getStudioVersion,D as installStudio,M as isStudioInstalled,C as promptAndInstallStudio};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{spawn as
|
|
1
|
+
import{spawn as n}from"child_process";import p from"os";import c from"path";import{existsSync as d,mkdirSync as a,writeFileSync as f}from"fs";import{getStudioAppPath as u}from"./studio-installer.js";const s=c.join(p.homedir(),".zibby","studio-launch.json");function l(r){const o={};r.projectRoot&&(o.projectRoot=r.projectRoot),r.port&&(o.port=r.port),o.launchedAt=Date.now();const t=c.dirname(s);a(t,{recursive:!0}),f(s,JSON.stringify(o,null,2))}function g(r={}){const o=u();if(!o)throw new Error("Studio not installed");if(!d(o))throw new Error(`Studio app not found at ${o}`);l(r);let t;p.platform()==="darwin"?t=n("open",["-a",o,"--env",`ZIBBY_STUDIO_PROJECT_ROOT=${r.projectRoot||process.cwd()}`],{stdio:["ignore","pipe","pipe"]}):(t=n(o,[],{detached:!0,stdio:["ignore","pipe","pipe"],env:{...process.env,ZIBBY_STUDIO_PROJECT_ROOT:r.projectRoot||process.cwd()}}),t.unref()),t.stderr?.on("data",e=>{const i=e.toString().trim();i&&console.error(`[Studio Desktop] ${i}`)}),t.on("error",e=>{console.error(`Failed to open Zibby Studio: ${e.message}`)}),t.on("close",e=>{e&&e!==0&&console.error(`Zibby Studio exited with code ${e}`)})}export{g as launchStudio};
|