@zibby/cli 0.1.30 → 0.1.31

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/zibby.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- process.stdout.on("error",o=>{o.code}),process.stderr.on("error",o=>{o.code}),process.env.DOTENV_CONFIG_QUIET="true";import"@zibby/skills";import{Command as s}from"commander";import{initCommand as c}from"../src/commands/init.js";import{runCommand as p}from"../src/commands/run.js";import{videoCommand as d}from"../src/commands/video.js";import{uploadCommand as m}from"../src/commands/upload.js";import{ciSetupCommand as l}from"../src/commands/ci-setup.js";import{setupPlaywrightMcpCommand as u,setupCiCommand as f,testWithVideoCommand as y}from"../src/commands/setup-scripts.js";import{readFileSync as w}from"fs";import{fileURLToPath as g}from"url";import{dirname as h,join as b}from"path";const k=g(import.meta.url),C=h(k),v=JSON.parse(w(b(C,"../package.json"),"utf-8")),i=process.argv.slice(2),I=i.includes("-h")||i.includes("--help"),P=i.includes("-V")||i.includes("--version"),S=i[0]==="chat",_=["--verbose","-v","--agent","--stream","-s"],j=i.length>0&&i.every(o=>_.includes(o)||i[i.indexOf(o)-1]==="--agent"),D=i.length===0||S||j;if(D&&!I&&!P){const o={},t=i.indexOf("--agent");t!==-1&&i[t+1]&&(o.agent=i[t+1]),(i.includes("--verbose")||i.includes("-v"))&&(o.verbose=!0),(i.includes("--stream")||i.includes("-s"))&&(o.stream=!0);const{chatCommand:r}=await import("../src/commands/chat.js");await r(o),process.exit(0)}const e=new s;e.name("zibby").description("Zibby Test Automation - AI-powered test generation").version(v.version),e.command("init").description("Initialize a new Zibby test project (like rails new)").argument("[project-name]","Project name (optional, uses current directory if not provided)").option("--agent <type>","Agent to use (claude, cursor, codex, gemini)").option("--memory-backend <backend>","Memory backend to configure (dolt, mem0)","dolt").option("--skip-install","Skip npm install").option("--skip-memory","Skip test memory setup during initialization").option("-f, --force","Force reinitialize (overwrite existing config)").option("--headed","Run MCP browser in headed mode (visible browser)").option("--headless","Run MCP browser in headless mode (hidden browser)").option("--api-key <key>","Zibby API key for cloud sync").option("--cloud-sync","Enable cloud sync and install Zibby MCP").action(c),e.command("test").description("Run a test specification").argument("[spec-path]","Path to test spec file or inline test description in quotes").option("--sources <ids>","Comma-separated test case IDs to fetch from cloud").option("--execution <id>","Execution ID containing the test cases (required with --sources)").option("--agent <type>","Agent to use (claude, cursor, codex, gemini) - overrides config").option("--workflow <name>","Workflow to use (e.g., QuickSmokeWorkflow, quick-smoke)").option("--headless","Run browser in headless mode").option("--node <name>","Run only a specific node (e.g., execute_live, generate_script)").option("--session <id>",'Use existing session (e.g., 1768974629717 or "last") - requires --node').option("--session-path <dir>","Use this session folder (absolute or relative to cwd); Studio pins artifacts here").option("--project <id>","Project ID (optional, auto-detected from ZIBBY_API_KEY)").option("--collection <id-or-name>","Collection ID or name (creates new if name doesn't exist)").option("--folder <path>","Folder path within collection (optional, requires --collection)").option("--sync","Force upload to cloud (overrides cloudSync: false)").option("--no-sync","Skip upload to cloud (overrides cloudSync: true)").option("--config <path>","Path to config file",".zibby.config.mjs").option("--auto-approve","Auto-approve MCP tools (for CI/CD)").option("-o, --open","Open test results in browser after completion").option("--verbose","Show info level logs").option("--debug","Show debug level logs (most verbose)").option("-m, --mem","Enable test memory (Dolt-backed knowledge from previous runs)").action((o,t)=>(t.debug?process.env.ZIBBY_DEBUG="true":t.verbose&&(process.env.ZIBBY_VERBOSE="true"),p(o,t))),e.command("implement").description("Implement a Jira ticket using AI agent (runs in ECS container)").action(async(...o)=>{const{implementCommand:t}=await import("../src/commands/implement.js");return t(...o)}),e.command("analyze").description("Analyze a Jira ticket against the codebase (runs in ECS container)").option("--workflow <path>","Path to a local workflow JSON file (e.g., .zibby/workflow-analysis.json)").action(async(...o)=>{const{analyzeCommand:t}=await import("../src/commands/analyze-graph.js");return t(...o)}),e.command("video").description("Organize test videos next to test files").action(d),e.command("upload <spec-path>").description("Upload existing test artifacts to Zibby Cloud").option("--project <id>","Project ID (REQUIRED - use flag or ZIBBY_PROJECT_ID env)").option("--collection <id-or-name>","Collection ID or name (creates new if name doesn't exist)").option("--folder <path>","Folder path within collection (optional)").option("--agent <type>","Agent used (for metadata)").action(m),e.command("login").description("Log in to Zibby (opens browser for authentication)").action(async()=>{const{loginCli:o}=await import("../src/auth/cli-login.js");await o(),process.exit(0)}),e.command("logout").description("Log out from Zibby (clears saved session)").action(async()=>{const{logoutCli:o}=await import("../src/auth/cli-login.js");o(),process.exit(0)}),e.command("status").description("Show current authentication status and token details").option("--json","Output in JSON format (for scripts)").action(async o=>{const{showLoginStatus:t}=await import("../src/auth/cli-login.js");await t(o),process.exit(0)}),e.command("list").description("List your projects and API tokens").action(async()=>{const{listProjectsCommand:o}=await import("../src/commands/list-projects.js");await o()}),e.command("ci-setup").description("Setup Cursor Agent for CI/CD (patch and configure)").option("--get-keys","Get MCP approval keys").option("--save","Save approval keys to project").action(l),e.command("setup-playwright").description("Setup official Playwright MCP (from cursor-agent-package)").option("--headed","Configure MCP in headed mode (visible browser)").option("--viewport-width <width>","Viewport width (default: 1280)","1280").option("--viewport-height <height>","Viewport height (default: 720)","720").action(o=>{const t={width:parseInt(o.viewportWidth,10)||1280,height:parseInt(o.viewportHeight,10)||720};return u({...o,viewport:t})}),e.command("setup-ci-full").description("Complete CI/CD setup from scratch").action(f),e.command("test-video").description("Run Playwright tests with video recording").argument("[test-file]","Test file to run (default: tests/)").option("--headed","Run in headed mode (visible browser)").action(y),e.command("generate").description("Generate test specs from a ticket + codebase (local analysis using real AI agent)").option("-t, --ticket <key>","Jira ticket key (fetches automatically)").option("-i, --input <file>","Input file with ticket description/requirements").option("-d, --description <text>","Inline ticket description").option("--repo <path>","Path to the codebase (default: current directory)").option("--agent <type>","Agent to use (codex, claude, cursor, gemini)").option("--model <model>","Model override").option("-o, --output <dir>","Output directory for spec files (default: test-specs)").action(async o=>{const{generateCommand:t}=await import("../src/commands/generate.js");return t(o)}),e.command("studio").description("Launch Zibby Studio desktop (installs from CDN if needed). Uses this folder as the Zibby project.").option("-p, --port <port>","Port for the Studio API bridge (default: 3847)").option("--web","Open Studio in the browser instead of the desktop app").option("--no-open","Start the API only; do not open desktop or browser").action(async o=>{const{studioCommand:t}=await import("../src/commands/studio.js");return t(o)});const n=e.command("memory").description("Test memory database \u2014 version-controlled knowledge from runs");n.command("stats").description("Show memory database statistics").action(async()=>{const{memoryStatsCommand:o}=await import("../src/commands/memory.js");return o()}),n.command("init").description("Initialize the memory database (Dolt)").action(async()=>{const{memoryInitCommand:o}=await import("../src/commands/memory.js");return o()}),n.command("compact").description("Prune old data and run Dolt GC to reclaim storage").option("--max-runs <n>","Keep last N runs per spec (default: 50)",parseInt).option("--max-age <days>","Remove data older than N days (default: 90)",parseInt).action(async o=>{const{memoryCompactCommand:t}=await import("../src/commands/memory.js");return t(o)}),n.command("reset").description("Wipe the memory database").option("-f, --force","Confirm reset").action(async o=>{const{memoryResetCommand:t}=await import("../src/commands/memory.js");return t(o)});const a=e.command("workflow").description("Manage workflow graphs (download, upload, list)");a.command("download").description("Download a workflow graph from Zibby Cloud to .zibby/").option("--project <id>","Project ID (or ZIBBY_PROJECT_ID env)").option("--type <type>","Workflow type: analysis, implementation, run_test").option("--api-key <key>","API key (or ZIBBY_API_KEY env)").option("--output <dir>","Output directory (default: .zibby/)").option("--include-default","Download the built-in default graph if no custom one exists").action(async o=>{const{workflowDownloadCommand:t}=await import("../src/commands/workflow.js");return t(o)}),a.command("upload").description("Upload a local workflow graph to Zibby Cloud (validates before upload)").option("--project <id>","Project ID (or ZIBBY_PROJECT_ID env)").option("--type <type>","Workflow type: analysis, implementation, run_test").option("--api-key <key>","API key (or ZIBBY_API_KEY env)").option("--file <path>","Path to workflow JSON (default: .zibby/workflow-<type>.json)").action(async o=>{const{workflowUploadCommand:t}=await import("../src/commands/workflow.js");return t(o)}),a.command("list").description("List all workflows for a project").option("--project <id>","Project ID (or ZIBBY_PROJECT_ID env)").option("--api-key <key>","API key (or ZIBBY_API_KEY env)").action(async o=>{const{workflowListCommand:t}=await import("../src/commands/workflow.js");return t(o)});const x=e.command("run-queue").description("Parallel capacity \u2014 on-disk wait queue (see parallel.waitWhenAtCapacity in .zibby.config.mjs)");x.command("list").description("List CLI processes waiting for a run slot").option("--config <path>","Path to config file",".zibby.config.mjs").action(async o=>{const{runCapacityQueueListCommand:t}=await import("../src/commands/run-capacity-queue-cli.js");await t(o),process.exit(0)}),e.parse();
2
+ process.stdout.on("error",o=>{o.code}),process.stderr.on("error",o=>{o.code}),process.env.DOTENV_CONFIG_QUIET="true";import"@zibby/skills";import{Command as s}from"commander";import{initCommand as c}from"../commands/init.js";import{runCommand as p}from"../commands/run.js";import{videoCommand as d}from"../commands/video.js";import{uploadCommand as m}from"../commands/upload.js";import{ciSetupCommand as l}from"../commands/ci-setup.js";import{setupPlaywrightMcpCommand as u,setupCiCommand as f,testWithVideoCommand as y}from"../commands/setup-scripts.js";import{readFileSync as w}from"fs";import{fileURLToPath as g}from"url";import{dirname as h,join as b}from"path";const k=g(import.meta.url),C=h(k),v=JSON.parse(w(b(C,"../package.json"),"utf-8")),i=process.argv.slice(2),I=i.includes("-h")||i.includes("--help"),P=i.includes("-V")||i.includes("--version"),S=i[0]==="chat",_=["--verbose","-v","--agent","--stream","-s"],j=i.length>0&&i.every(o=>_.includes(o)||i[i.indexOf(o)-1]==="--agent"),D=i.length===0||S||j;if(D&&!I&&!P){const o={},t=i.indexOf("--agent");t!==-1&&i[t+1]&&(o.agent=i[t+1]),(i.includes("--verbose")||i.includes("-v"))&&(o.verbose=!0),(i.includes("--stream")||i.includes("-s"))&&(o.stream=!0);const{chatCommand:r}=await import("../commands/chat.js");await r(o),process.exit(0)}const e=new s;e.name("zibby").description("Zibby Test Automation - AI-powered test generation").version(v.version),e.command("init").description("Initialize a new Zibby test project (like rails new)").argument("[project-name]","Project name (optional, uses current directory if not provided)").option("--agent <type>","Agent to use (claude, cursor, codex, gemini)").option("--memory-backend <backend>","Memory backend to configure (dolt, mem0)","dolt").option("--skip-install","Skip npm install").option("--skip-memory","Skip test memory setup during initialization").option("-f, --force","Force reinitialize (overwrite existing config)").option("--headed","Run MCP browser in headed mode (visible browser)").option("--headless","Run MCP browser in headless mode (hidden browser)").option("--api-key <key>","Zibby API key for cloud sync").option("--cloud-sync","Enable cloud sync and install Zibby MCP").action(c),e.command("test").description("Run a test specification").argument("[spec-path]","Path to test spec file or inline test description in quotes").option("--sources <ids>","Comma-separated test case IDs to fetch from cloud").option("--execution <id>","Execution ID containing the test cases (required with --sources)").option("--agent <type>","Agent to use (claude, cursor, codex, gemini) - overrides config").option("--workflow <name>","Workflow to use (e.g., QuickSmokeWorkflow, quick-smoke)").option("--headless","Run browser in headless mode").option("--node <name>","Run only a specific node (e.g., execute_live, generate_script)").option("--session <id>",'Use existing session (e.g., 1768974629717 or "last") - requires --node').option("--session-path <dir>","Use this session folder (absolute or relative to cwd); Studio pins artifacts here").option("--project <id>","Project ID (optional, auto-detected from ZIBBY_API_KEY)").option("--collection <id-or-name>","Collection ID or name (creates new if name doesn't exist)").option("--folder <path>","Folder path within collection (optional, requires --collection)").option("--sync","Force upload to cloud (overrides cloudSync: false)").option("--no-sync","Skip upload to cloud (overrides cloudSync: true)").option("--config <path>","Path to config file",".zibby.config.mjs").option("--auto-approve","Auto-approve MCP tools (for CI/CD)").option("-o, --open","Open test results in browser after completion").option("--verbose","Show info level logs").option("--debug","Show debug level logs (most verbose)").option("-m, --mem","Enable test memory (Dolt-backed knowledge from previous runs)").action((o,t)=>(t.debug?process.env.ZIBBY_DEBUG="true":t.verbose&&(process.env.ZIBBY_VERBOSE="true"),p(o,t))),e.command("implement").description("Implement a Jira ticket using AI agent (runs in ECS container)").action(async(...o)=>{const{implementCommand:t}=await import("../commands/implement.js");return t(...o)}),e.command("analyze").description("Analyze a Jira ticket against the codebase (runs in ECS container)").option("--workflow <path>","Path to a local workflow JSON file (e.g., .zibby/workflow-analysis.json)").action(async(...o)=>{const{analyzeCommand:t}=await import("../commands/analyze-graph.js");return t(...o)}),e.command("video").description("Organize test videos next to test files").action(d),e.command("upload <spec-path>").description("Upload existing test artifacts to Zibby Cloud").option("--project <id>","Project ID (REQUIRED - use flag or ZIBBY_PROJECT_ID env)").option("--collection <id-or-name>","Collection ID or name (creates new if name doesn't exist)").option("--folder <path>","Folder path within collection (optional)").option("--agent <type>","Agent used (for metadata)").action(m),e.command("login").description("Log in to Zibby (opens browser for authentication)").action(async()=>{const{loginCli:o}=await import("../auth/cli-login.js");await o(),process.exit(0)}),e.command("logout").description("Log out from Zibby (clears saved session)").action(async()=>{const{logoutCli:o}=await import("../auth/cli-login.js");o(),process.exit(0)}),e.command("status").description("Show current authentication status and token details").option("--json","Output in JSON format (for scripts)").action(async o=>{const{showLoginStatus:t}=await import("../auth/cli-login.js");await t(o),process.exit(0)}),e.command("list").description("List your projects and API tokens").action(async()=>{const{listProjectsCommand:o}=await import("../commands/list-projects.js");await o()}),e.command("ci-setup").description("Setup Cursor Agent for CI/CD (patch and configure)").option("--get-keys","Get MCP approval keys").option("--save","Save approval keys to project").action(l),e.command("setup-playwright").description("Setup official Playwright MCP (from cursor-agent-package)").option("--headed","Configure MCP in headed mode (visible browser)").option("--viewport-width <width>","Viewport width (default: 1280)","1280").option("--viewport-height <height>","Viewport height (default: 720)","720").action(o=>{const t={width:parseInt(o.viewportWidth,10)||1280,height:parseInt(o.viewportHeight,10)||720};return u({...o,viewport:t})}),e.command("setup-ci-full").description("Complete CI/CD setup from scratch").action(f),e.command("test-video").description("Run Playwright tests with video recording").argument("[test-file]","Test file to run (default: tests/)").option("--headed","Run in headed mode (visible browser)").action(y),e.command("generate").description("Generate test specs from a ticket + codebase (local analysis using real AI agent)").option("-t, --ticket <key>","Jira ticket key (fetches automatically)").option("-i, --input <file>","Input file with ticket description/requirements").option("-d, --description <text>","Inline ticket description").option("--repo <path>","Path to the codebase (default: current directory)").option("--agent <type>","Agent to use (codex, claude, cursor, gemini)").option("--model <model>","Model override").option("-o, --output <dir>","Output directory for spec files (default: test-specs)").action(async o=>{const{generateCommand:t}=await import("../commands/generate.js");return t(o)}),e.command("studio").description("Launch Zibby Studio desktop (installs from CDN if needed). Uses this folder as the Zibby project.").option("-p, --port <port>","Port for the Studio API bridge (default: 3847)").option("--no-open","Start the API only; do not launch desktop app").action(async o=>{const{studioCommand:t}=await import("../commands/studio.js");return t(o)});const n=e.command("memory").description("Test memory database \u2014 version-controlled knowledge from runs");n.command("stats").description("Show memory database statistics").action(async()=>{const{memoryStatsCommand:o}=await import("../commands/memory.js");return o()}),n.command("init").description("Initialize the memory database (Dolt)").action(async()=>{const{memoryInitCommand:o}=await import("../commands/memory.js");return o()}),n.command("compact").description("Prune old data and run Dolt GC to reclaim storage").option("--max-runs <n>","Keep last N runs per spec (default: 50)",parseInt).option("--max-age <days>","Remove data older than N days (default: 90)",parseInt).action(async o=>{const{memoryCompactCommand:t}=await import("../commands/memory.js");return t(o)}),n.command("reset").description("Wipe the memory database").option("-f, --force","Confirm reset").action(async o=>{const{memoryResetCommand:t}=await import("../commands/memory.js");return t(o)});const a=e.command("workflow").description("Manage workflow graphs (download, upload, list)");a.command("download").description("Download a workflow graph from Zibby Cloud to .zibby/").option("--project <id>","Project ID (or ZIBBY_PROJECT_ID env)").option("--type <type>","Workflow type: analysis, implementation, run_test").option("--api-key <key>","API key (or ZIBBY_API_KEY env)").option("--output <dir>","Output directory (default: .zibby/)").option("--include-default","Download the built-in default graph if no custom one exists").action(async o=>{const{workflowDownloadCommand:t}=await import("../commands/workflow.js");return t(o)}),a.command("upload").description("Upload a local workflow graph to Zibby Cloud (validates before upload)").option("--project <id>","Project ID (or ZIBBY_PROJECT_ID env)").option("--type <type>","Workflow type: analysis, implementation, run_test").option("--api-key <key>","API key (or ZIBBY_API_KEY env)").option("--file <path>","Path to workflow JSON (default: .zibby/workflow-<type>.json)").action(async o=>{const{workflowUploadCommand:t}=await import("../commands/workflow.js");return t(o)}),a.command("list").description("List all workflows for a project").option("--project <id>","Project ID (or ZIBBY_PROJECT_ID env)").option("--api-key <key>","API key (or ZIBBY_API_KEY env)").action(async o=>{const{workflowListCommand:t}=await import("../commands/workflow.js");return t(o)});const x=e.command("run-queue").description("Parallel capacity \u2014 on-disk wait queue (see parallel.waitWhenAtCapacity in .zibby.config.mjs)");x.command("list").description("List CLI processes waiting for a run slot").option("--config <path>","Path to config file",".zibby.config.mjs").action(async o=>{const{runCapacityQueueListCommand:t}=await import("../commands/run-capacity-queue-cli.js");await t(o),process.exit(0)}),e.parse();
@@ -1,33 +1,19 @@
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 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
- `)){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 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
- `,"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 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
- ${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(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(`
2
+ import St from"express";import{createServer as Jt}from"http";import{WebSocketServer as Mt}from"ws";import{spawn as nt,execSync as Y}from"child_process";import{readFileSync as k,existsSync as u,readdirSync as $,statSync as L,createReadStream as Kt,createWriteStream as Yt,mkdirSync as It,openSync as Ht,readSync as Zt,closeSync as Vt,writeFileSync as H,unlinkSync as qt}from"fs";import{join as a,resolve as C,dirname as bt,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 jt,readLatestLiveFramePayloadSync as Pt}from"@zibby/core/utils/live-frame-discovery.js";import{getApiUrl as vt}from"../config/environments.js";const le=ee(import.meta.url),ue=bt(le);function wt(A){const g=a(A,"video.webm");if(u(g))return g;const S=a(A,"execute_live");if(!u(S))return null;try{const E=$(S).filter(I=>I.endsWith(".webm"));return E.length===0?null:E.map(I=>{const v=a(S,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 Tt(A){if(process.platform==="win32")try{const g=Y("netstat -ano",{encoding:"utf8"}),S=new Set;for(const E of g.split(`
3
+ `)){if(!E.includes("LISTENING"))continue;const _=E.trim().split(/\s+/);if(!(_[1]||"").endsWith(`:${A}`))continue;const v=_[_.length-1];/^\d+$/.test(v)&&S.add(parseInt(v,10))}return[...S].filter(E=>E!==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(S=>parseInt(S,10)).filter(S=>!Number.isNaN(S)&&S!==process.pid)}catch{return[]}}async function Nt(A){let g=Tt(A);if(g.length!==0){console.log(`[Studio] Port ${A} in use \u2014 stopping previous Studio listener(s): ${g.join(", ")}`);for(const S of g)try{process.kill(S,"SIGTERM")}catch{}await new Promise(S=>setTimeout(S,450)),g=Tt(A);for(const S of g)try{process.kill(S,"SIGKILL")}catch{}await new Promise(S=>setTimeout(S,200))}}async function ke(A={}){const g=St(),S=Jt(g),E=new Mt({server:S}),_=A.port||3847,I=process.cwd(),v=Xt();process.env.DOTENV_CONFIG_QUIET="true";const At=process.env.NODE_ENV||"development";[C(I,".env.local"),C(I,`.env.${At}`),C(I,".env")].forEach(s=>{u(s)&&ne.config({path:s,override:!1})});const R=new Map,D=new Map;let _t=null;const G=96e3,ot="studio-cli.log",U="studio-run.json",kt=".zibby-studio-stop",Z=512*1024;g.use(St.json()),g.get("/api/session-run-states",(s,t)=>{try{let e=O();try{const r=new URL(s.url,"http://zibby.studio").searchParams.get("sessionsRoot");if(r&&String(r).trim()){const i=C(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"),Et=u(a(V,"package.json"));function Ot(){const s=a(V,"node_modules",".bin",process.platform==="win32"?"electron.cmd":"electron"),t=u(s),n=nt(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:${_}/api`}});n.unref(),_t=n}async function Ct(){if(Et){Ot();return}!re()&&!await oe()||ie({port:_,sessionsDir:O()})}function q(){const s=a(I,".zibby.config.mjs");return u(s)?s:a(v,".zibby.config.mjs")}function O(){q();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(k(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 xt(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 xt(c))n(l);try{process.kill(c,t)}catch{}}}n(e)}function rt(s){const t=Number(s);if(!(!Number.isFinite(t)||t<=0))try{Y(`taskkill /PID ${t} /T /F`,{stdio:"ignore",windowsHide:!0})}catch{}}function it(s){if(!Number.isFinite(s)||s<=0)return;if(process.platform==="win32"){rt(s);return}W(s,"SIGTERM");const t=setTimeout(()=>{W(s,"SIGKILL")},800);typeof t.unref=="function"&&t.unref()}function Ft(s,t){const o=x(s)||a(O(),s);try{It(o,{recursive:!0}),H(a(o,kt),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")rt(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(k(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 it(r),!0}const l=Number(t);return Number.isFinite(l)&&l>0?(it(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=C(o);return u(n)?n:""}catch{return""}}function x(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=C(h);r.has(f)||(r.add(f),i.push(f))},p=h=>{if(h){d(h);for(const f of o){const y=a(h,f);c.has(y)||(c.add(y),l.push(y))}}};p(t),p(O()),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 y;try{y=$(f,{withFileTypes:!0})}catch{y=[]}for(const T of y)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 y of f){if(!y?.isDirectory?.())continue;const T=String(y.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),jt(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 y=a(h,"execute_live");u(y)&&(f+=Math.min(L(y).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=bt(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=Pt(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=q();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(k(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(vt()).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)}
5
+ `,"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(k(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(vt()).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)}
6
+ `,"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]?C(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=O(),r=a(l,n),i=C(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,y)=>{D.delete(n),w(),p||t.status(f).json(y)},h=()=>{const f=[];return E.clients.forEach(y=>{y.sessionId===n&&f.push(y)}),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]),It(r,{recursive:!0}),m=Yt(a(r,ot),{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 y=nt(f.cmd,f.args,{cwd:I,shell:f.useShell,env:{...process.env,ZIBBY_SESSION_ID:n,ZIBBY_SESSION_PATH:C(r)}});R.set(n,y),Rt(r,n,y.pid),B(i,{sessionId:String(n),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:I,outputBase:".zibby/output",sessionPathAbs:i,pid:y.pid??null});let T="",F="",ut=!1,dt=0;const ft=setInterval(()=>{try{const b=jt(r);if(!b||b.mtime<=dt)return;dt=b.mtime;const j=k(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),pt=b=>{if(ut)return;ut=!0,w();const j={pending:!1,...b};typeof j.stdout=="string"&&j.stdout.length>G&&(j.stdout=`\u2026(truncated)
7
+ ${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{}})};y.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{}})}),y.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{}})}),y.on("error",b=>{clearInterval(ft),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),pt({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}})}),y.on("close",b=>{clearInterval(ft),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"),mt=a(r,"result.json");if(u(K))try{const N=JSON.parse(k(K,"utf8"));P=N.name||N.task}catch(N){console.error("[Studio API] Failed to read session info:",N.message)}else if(u(mt))try{const N=JSON.parse(k(mt,"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 yt=T.match(N);if(yt){P=yt[1].trim();break}}const Wt=Q(r,I),et=wt(r),gt=a(r,"events.json"),ht=a(r,"execute_live","events.json"),st=u(gt)?gt:u(ht)?ht:null;pt({heldUntilExit:!0,success:j,exitCode:b,stdout:T,stderr:F,runName:P,videoPath:et&&u(et)?et:null,eventsPath:st&&u(st)?st:null,codegenFiles:Wt,metadata:{sessionId:n,task:e}})}),p=!0,t.status(202).json({accepted:!0,sessionId:n,pid:y.pid!=null&&Number.isFinite(Number(y.pid))?Number(y.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=O();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(k(r,"utf8")):u(i)&&(d=JSON.parse(k(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=x(e,n),l=a(c||a(O(),e),ot);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=x(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(k(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=x(e,o);if(!n)return t.json({ok:!1,error:"session_not_found"});const l=Pt(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=x(e,o);if(!n)return t.status(404).json({error:"Session not found"});const c=wt(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=x(e)||a(O(),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(k(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=x(e)||a(O(),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(k(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=nt(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 X=(s,t)=>{const e={type:s,message:t,time:new Date().toLocaleTimeString()};E.clients.forEach(o=>{if(o.listenType==="console")try{o.send(JSON.stringify(e))}catch{}})},zt=console.log,Bt=console.error,Dt=console.warn,tt=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),X("info",tt(s))},console.error=(...s)=>{Bt(...s),X("error",tt(s))},console.warn=(...s)=>{Dt(...s),X("warn",tt(s))},E.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=()=>{console.log(`
10
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\u2550\u2557
11
- \u2551 \u{1F525} Zibby Studio (DEV MODE) \u2551
10
+ \u2551 \u{1F3AD} Zibby Studio \u2551
12
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\u2550\u255D
13
12
 
14
- Frontend: http://localhost:${G} (with hot reload)
15
- API: http://localhost:${w}/api
16
- Project: ${S}
17
- Config: ${H()}
13
+ API: http://localhost:${_}/api
14
+ Project: ${I}
15
+ Config: ${q()}
18
16
 
19
- \u26A1 Hot reload enabled
20
- ${W?"\u{1F680} Vite auto-started":"\u2705 Using existing Vite server"}
21
17
  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?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
- \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
- \u2551 \u{1F3AD} Zibby Studio Web \u2551
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
26
-
27
- Local: http://localhost:${w}
28
- Project: ${S}
29
- Config: ${H()}
30
-
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?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
- 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};
18
+ `),console.log("[Studio] POST /api/run returns 202 immediately; poll GET /api/run/result/:sessionId (avoids gateway timeouts on long runs)."),A.open!==!1&&Ct().catch(s=>{console.error(`[Studio] Failed to launch desktop app: ${s.message}`)})};let ct=!1;function at(){const s=async t=>{if(S.removeListener("error",s),t.code==="EADDRINUSE"&&!ct){ct=!0,console.log(`[Studio] Port ${_} still busy \u2014 forcing cleanup and retrying...`),await Nt(_),at();return}console.error("[Studio] Server error:",t.message),process.exit(1)};S.once("error",s),S.listen(_,()=>{S.removeListener("error",s),S.on("error",t=>{console.error("[Studio] Runtime error:",t.message)}),Gt()})}await Nt(_),at();const lt=()=>{console.log(`
19
+ Stopping Studio...`);const s=O();for(const t of R.keys()){const e=C(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()),S.close(()=>{console.log("Studio stopped"),process.exit(0)})};process.on("SIGINT",lt),process.on("SIGTERM",lt)}export{ke as studioCommand};
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@zibby/cli",
3
+ "version": "0.1.31",
4
+ "description": "Zibby CLI - Test automation generator and runner",
5
+ "type": "module",
6
+ "bin": {
7
+ "zibby": "./bin/zibby.js"
8
+ },
9
+ "scripts": {
10
+ "build": "node ../scripts/build.mjs --extra-dirs bin",
11
+ "test": "vitest run test/auth*.test.js test/two-layer-auth.test.js",
12
+ "test:unit": "vitest run src/",
13
+ "test:auth": "vitest run test/auth*.test.js test/two-layer-auth.test.js",
14
+ "lint": "eslint .",
15
+ "lint:fix": "eslint --fix ."
16
+ },
17
+ "keywords": [
18
+ "testing",
19
+ "automation",
20
+ "e2e",
21
+ "playwright",
22
+ "ai"
23
+ ],
24
+ "author": "Zibby",
25
+ "license": "MIT",
26
+ "homepage": "https://zibby.app",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/ZibbyHQ/zibby-agent"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/ZibbyHQ/zibby-agent/issues"
33
+ },
34
+ "dependencies": {
35
+ "@aws-sdk/client-sqs": "^3.1029.0",
36
+ "@zibby/core": "^0.1.24",
37
+ "@zibby/memory": "^0.1.4",
38
+ "@zibby/skills": "^0.1.4",
39
+ "adm-zip": "^0.5.17",
40
+ "chalk": "^5.3.0",
41
+ "commander": "^12.0.0",
42
+ "dotenv": "^17.4.1",
43
+ "express": "^4.18.2",
44
+ "glob": "^13.0.0",
45
+ "handlebars": "^4.7.9",
46
+ "inquirer": "^13.4.1",
47
+ "mem0ai": "npm:@zibby/mem0ai@^2.4.7",
48
+ "open": "^10.2.0",
49
+ "ora": "^8.0.1",
50
+ "tar": "^7.5.2",
51
+ "ws": "^8.20.0"
52
+ },
53
+ "files": [
54
+ "bin/",
55
+ "src/",
56
+ "README.md",
57
+ "LICENSE"
58
+ ],
59
+ "engines": {
60
+ "node": ">=18.0.0"
61
+ },
62
+ "devDependencies": {
63
+ "esbuild": "^0.28.0",
64
+ "vitest": "^4.1.4"
65
+ }
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zibby/cli",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "description": "Zibby CLI - Test automation generator and runner",
5
5
  "type": "module",
6
6
  "bin": {
@@ -33,7 +33,7 @@
33
33
  },
34
34
  "dependencies": {
35
35
  "@aws-sdk/client-sqs": "^3.1029.0",
36
- "@zibby/core": "^0.1.22",
36
+ "@zibby/core": "^0.1.24",
37
37
  "@zibby/memory": "^0.1.4",
38
38
  "@zibby/skills": "^0.1.4",
39
39
  "adm-zip": "^0.5.17",