@zibby/cli 0.1.39 → 0.1.42
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 +1 -1
- package/dist/commands/init.js +30 -29
- package/dist/commands/studio.js +10 -8
- package/dist/commands/workflow.js +21 -21
- package/dist/commands/workflows/deploy.js +25 -0
- package/dist/commands/workflows/generate.js +58 -0
- package/dist/commands/workflows/run.js +8 -0
- package/dist/commands/workflows/start.js +16 -0
- package/dist/package.json +1 -1
- package/package.json +1 -1
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"../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();
|
|
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 d}from"../commands/run.js";import{videoCommand as p}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 w}from"../commands/setup-scripts.js";import{readFileSync as y}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(y(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",D=["--verbose","-v","--agent","--stream","-s"],_=i.length>0&&i.every(o=>D.includes(o)||i[i.indexOf(o)-1]==="--agent"),E=i.length===0||S||_;if(E&&!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:n}=await import("../commands/chat.js");await n(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"),d(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(p),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(w),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").option("--update","Force re-download of the latest Studio binary").action(async o=>{const{studioCommand:t}=await import("../commands/studio.js");return t(o)});const j=e.command("g").description("Generate scaffolds");j.command("workflow [name]").description("Scaffold a new custom workflow in .zibby/workflows/<name>/ (auto-generates name if omitted)").action(async o=>{const{generateWorkflowCommand:t}=await import("../commands/workflows/generate.js");return t(o)}),e.command("start <workflow-name>").description("Start a local dev server for a custom workflow").option("-p, --port <port>","Port for the workflow server (default: 3848)").action(async(o,t)=>{const{startWorkflowCommand:n}=await import("../commands/workflows/start.js");return n(o,t)}),e.command("deploy <workflow-name>").description("Deploy a custom workflow to Zibby Cloud").option("--project <id>","Project ID (or ZIBBY_PROJECT_ID env)").option("--api-key <key>","API key (or ZIBBY_API_KEY env)").action(async(o,t)=>{const{deployWorkflowCommand:n}=await import("../commands/workflows/deploy.js");return n(o,t)}),e.command("run-workflow").description("Run a deployed workflow from S3 sources (used by ECS containers)").action(async()=>{const{runWorkflowCommand:o}=await import("../commands/workflows/run.js");return o()});const a=e.command("memory").description("Test memory database \u2014 version-controlled knowledge from runs");a.command("stats").description("Show memory database statistics").action(async()=>{const{memoryStatsCommand:o}=await import("../commands/memory.js");return o()}),a.command("init").description("Initialize the memory database (Dolt)").action(async()=>{const{memoryInitCommand:o}=await import("../commands/memory.js");return o()}),a.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)}),a.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 r=e.command("workflow").description("Manage workflow graphs (download, upload, list)");r.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)}),r.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();
|
package/dist/commands/init.js
CHANGED
|
@@ -1,39 +1,40 @@
|
|
|
1
|
-
import{mkdir as
|
|
1
|
+
import{mkdir as A,writeFile as u,readFile as w}from"fs/promises";import{existsSync as y,readdirSync as O}from"fs";import{join as i,resolve as L,dirname as Z}from"path";import{homedir as P}from"os";import D from"inquirer";import e from"chalk";import I from"ora";import{spawn as M,execSync as $}from"child_process";import{fileURLToPath as j}from"url";const N=j(import.meta.url),G=Z(N);async function H(){try{const l=process.platform==="win32",n=$("npm config get prefix",{encoding:"utf-8"}).trim(),g=l?n:`${n}/bin`,d=g.replace(/^~/,P()),a=l?";":":";if(process.env.PATH.split(a).some(v=>{const B=v.replace(/^~/,P());return B===d||B===g}))return;console.log(e.yellow("\u26A0\uFE0F npm global bin not in PATH")),console.log(e.gray(` Location: ${d}`)),console.log();const{shouldAddPath:o}=await D.prompt([{type:"confirm",name:"shouldAddPath",message:"Add npm global bin to your shell PATH automatically?",default:!0}]);if(!o){l?(console.log(e.gray(`
|
|
2
2
|
\u{1F4A1} To add manually on Windows:`)),console.log(e.gray(' 1. Search "Environment Variables" in Start menu')),console.log(e.gray(' 2. Edit "Path" in User variables')),console.log(e.gray(` 3. Add: ${d}
|
|
3
3
|
`))):(console.log(e.gray(`
|
|
4
4
|
\u{1F4A1} To add manually, run:`)),console.log(e.gray(` echo 'export PATH="${d}:$PATH"' >> ~/.zshrc`)),console.log(e.gray(` source ~/.zshrc
|
|
5
5
|
`)));return}if(l){console.log(e.yellow("\u26A0\uFE0F Cannot auto-add PATH on Windows")),console.log(e.gray(" Please add manually:")),console.log(e.gray(' 1. Search "Environment Variables" in Start menu')),console.log(e.gray(' 2. Edit "Path" in User variables')),console.log(e.gray(` 3. Add: ${d}
|
|
6
|
-
`));return}const x=process.env.SHELL||"";let p="";if(x.includes("zsh"))p=
|
|
6
|
+
`));return}const x=process.env.SHELL||"";let p="";if(x.includes("zsh"))p=i(P(),".zshrc");else if(x.includes("bash"))p=y(i(P(),".bashrc"))?i(P(),".bashrc"):i(P(),".bash_profile");else{console.log(e.yellow(`\u26A0\uFE0F Unknown shell: ${x}`)),console.log(e.gray(" Please add manually:")),console.log(e.gray(` export PATH="${d}:$PATH"`));return}if(y(p)){const v=await w(p,"utf-8");if(v.includes(d)||v.includes("npm")&&v.includes("global")&&v.includes("bin")){console.log(e.yellow(`\u26A0\uFE0F PATH entry found in ${p} but not active`)),console.log(e.gray(` Run: source ${p}
|
|
7
7
|
`));return}}const b=`
|
|
8
8
|
# npm global bin (added by zibby)
|
|
9
9
|
export PATH="${d}:$PATH"
|
|
10
10
|
`;await u(p,(y(p)?await w(p,"utf-8"):"")+b),console.log(e.green(`\u2705 Added to ${p}`)),console.log(e.yellow(`
|
|
11
11
|
\u{1F4A1} Run this to activate in current session:`)),console.log(e.gray(` source ${p}
|
|
12
|
-
`))}catch{}}async function de(l,
|
|
12
|
+
`))}catch{}}async function de(l,n){console.log(e.bold.cyan(`
|
|
13
13
|
\u{1F3AD} Welcome to Zibby Test Automation!
|
|
14
|
-
`));const g=!
|
|
14
|
+
`));const g=!n.skipMemory,d=["dolt","mem0"].includes(String(n.memoryBackend||"").toLowerCase())?String(n.memoryBackend).toLowerCase():"dolt";await H();const a=l?L(process.cwd(),l):process.cwd(),E=l||"zibby-tests",f=!!l;f&&y(a)&&(console.log(e.red(`
|
|
15
15
|
\u274C Directory "${l}" already exists!
|
|
16
|
-
`)),process.exit(1)),!f&&y(
|
|
16
|
+
`)),process.exit(1)),!f&&y(i(a,".zibby.config.mjs"))&&!n.force&&(console.log(e.yellow(`
|
|
17
17
|
\u26A0\uFE0F Zibby is already initialized in this directory!
|
|
18
18
|
`)),console.log(e.white("Config file found: .zibby.config.mjs")),console.log(e.gray(`Use --force or -f to reinitialize
|
|
19
|
-
`)),process.exit(0)),
|
|
19
|
+
`)),process.exit(0)),n.force&&!f&&console.log(e.cyan(`
|
|
20
20
|
Reinitializing Zibby configuration...
|
|
21
|
-
`));let
|
|
22
|
-
`)),
|
|
21
|
+
`));let o;if(n.agent&&(n.headed||n.headless))console.log(e.cyan(`Setting up with provided options...
|
|
22
|
+
`)),o={agent:n.agent,browserMode:n.headless?"headless":"headed",apiKey:n.apiKey||null,cloudSync:!!(n.cloudSync||n.apiKey)};else{const b=[];n.agent||b.push({type:"select",name:"agent",message:"Which AI agent do you prefer?",choices:[{name:"Cursor",value:"cursor"},{name:"Claude (Anthropic)",value:"claude"},{name:"Codex (OpenAI)",value:"codex"},{name:"Gemini (Google)",value:"gemini"}],default:"cursor"}),!n.headed&&!n.headless&&b.push({type:"select",name:"browserMode",message:"Browser mode during live AI execution?",choices:[{name:"Headed - Visible browser (recommended for development)",value:"headed"},{name:"Headless - Hidden browser (for CI/CD)",value:"headless"}],default:"headed"}),n.apiKey||b.push({type:"input",name:"apiKey",message:"Enable cloud sync? Enter project ZIBBY_API_KEY (or press Enter to skip):"}),o=b.length>0?await D.prompt(b):{},o.agent=n.agent||o.agent,o.browserMode=n.headless?"headless":n.headed?"headed":o.browserMode,o.apiKey=n.apiKey||o.apiKey,o.cloudSync=!!(n.cloudSync||n.apiKey||o.apiKey&&o.apiKey.trim())}o.mcp="playwright",o.setupMcp=o.agent==="cursor";const p=I("Setting up Zibby...").start();try{if(f&&await A(a,{recursive:!0}),await A(i(a,"test-specs/examples"),{recursive:!0}),await A(i(a,"tests"),{recursive:!0}),await A(i(a,".zibby/output"),{recursive:!0}),await A(i(a,".zibby/commands"),{recursive:!0}),g&&d==="dolt")try{const{initMemory:t,DoltDB:r}=await import("@zibby/memory");if(r.isAvailable()){const{created:s}=t(a);s&&(p.text="Initialized test memory database (Dolt)...")}else p.text="Dolt not found \u2014 skipping memory database (brew install dolt)"}catch{}p.text="Scaffolding workflow graph...";const{TemplateFactory:b}=await import("@zibby/core/templates"),v=n.template||"browser-test-automation";try{const{graphPath:t,nodesPath:r,readmePath:s,resultHandlerPath:m,template:c}=b.getTemplateFiles(v),h=i(a,".zibby"),z=await w(t,"utf-8");if(await u(i(h,"graph.mjs"),z),m){const C=await w(m,"utf-8");await u(i(h,"result-handler.mjs"),C)}const Y=await w(s,"utf-8");await u(i(h,"README.md"),Y),await A(i(h,"nodes"),{recursive:!0});const{readdirSync:k}=await import("fs"),S=k(r);for(const C of S){let K=await w(i(r,C),"utf-8");!g&&C==="execute-live.mjs"&&(K=K.replace("skills: [SKILLS.BROWSER, SKILLS.MEMORY],","skills: [SKILLS.BROWSER],")),await u(i(h,"nodes",C),K)}const R=i(c.path,"chat.mjs");if(y(R)){const C=await w(R,"utf-8");await u(i(h,"chat.mjs"),C)}}catch(t){throw p.fail(`Failed to scaffold template: ${t.message}`),t}p.text="Generating configuration files...";const B=F(o,n,{memoryBackend:d});if(await u(i(a,".zibby.config.mjs"),B),await u(i(a,".env.example"),W(o,d)),o.apiKey&&o.apiKey.trim()){const t=i(a,".env"),r=o.apiKey.trim();if(y(t)){let s=await w(t,"utf8");/^ZIBBY_API_KEY=/m.test(s)?s=s.replace(/^ZIBBY_API_KEY=.*/m,`ZIBBY_API_KEY=${r}`):/^#\s*ZIBBY_API_KEY=/m.test(s)?s=s.replace(/^#\s*ZIBBY_API_KEY=.*/m,`ZIBBY_API_KEY=${r}`):s=`${s.trimEnd()}
|
|
23
23
|
|
|
24
24
|
# Zibby Cloud Sync
|
|
25
25
|
ZIBBY_API_KEY=${r}
|
|
26
|
-
`,await u(t,
|
|
26
|
+
`,await u(t,s)}else await u(t,U(o,r,d))}if(f){const t=V(E,o,{memoryBackend:d});await u(i(a,"package.json"),t)}if(!y(i(a,".gitignore"))){const t=q();await u(i(a,".gitignore"),t)}if(!y(i(a,"playwright.config.js"))){const t=X("on");await u(i(a,"playwright.config.js"),t)}if(!y(i(a,"test-specs/examples/example-domain.txt"))){const t=J();await u(i(a,"test-specs/examples/example-domain.txt"),t)}const T=L(G,"../../../../examples/.zibby/commands");if(y(T)){const t=O(T).filter(r=>r.toLowerCase().endsWith(".md"));for(const r of t){const s=i(a,".zibby/commands",r);if(n.force||!y(s)){const m=await w(i(T,r),"utf-8");await u(s,m)}}}if(!y(i(a,".zibby/commands/example.md"))){const t=Q();await u(i(a,".zibby/commands/example.md"),t)}if(f){const t=ee(E,o);await u(i(a,"README.md"),t)}if(p.succeed(f?"Project created!":"Zibby initialized!"),f&&!n.skipInstall){const t=I("Installing dependencies...").start();await new Promise((r,s)=>{M("npm",["install"],{cwd:a,stdio:"pipe"}).on("close",c=>{c===0?(t.succeed("Dependencies installed!"),r()):(t.fail("Failed to install dependencies"),s(new Error("npm install failed")))})})}else f||console.log(e.gray(`
|
|
27
27
|
Make sure @zibby/cli is installed in your package.json
|
|
28
28
|
`));if(!f&&g&&d==="mem0"&&(console.log(e.yellow(`
|
|
29
29
|
Using mem0 backend requires mem0ai in your project dependencies.`)),console.log(e.gray("Run this manually in your project when ready:")),console.log(e.white(` npm install mem0ai
|
|
30
|
-
`))),!
|
|
30
|
+
`))),!n.skipInstall){const t=I("Installing Playwright browsers...").start();await new Promise(r=>{const s=M("npx",["playwright","install","chromium"],{cwd:a,stdio:"pipe"});let m="";s.stdout.on("data",c=>{m+=c.toString()}),s.stderr.on("data",c=>{m+=c.toString()}),s.on("close",c=>{c===0?(m.includes("already installed")||m.includes("up to date")?t.succeed("Playwright browsers already installed"):t.succeed("Playwright browsers installed!"),r()):(t.warn("Could not verify Playwright browsers"),console.log(e.yellow(`
|
|
31
31
|
\u26A0\uFE0F If tests fail, run: npx playwright install
|
|
32
|
-
`)),r())})})}if(
|
|
33
|
-
`))}}}if(
|
|
34
|
-
`))}}const
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
`)),r())})})}if(o.agent==="cursor"&&!n.skipInstall){const t=I("Checking cursor-agent CLI...").start();try{$("cursor-agent --version",{encoding:"utf-8",timeout:5e3,stdio:"pipe"}),t.succeed("cursor-agent CLI already installed")}catch{t.text="Installing cursor-agent CLI...";try{await new Promise((r,s)=>{M("bash",["-c","curl https://cursor.com/install -fsS | bash"],{stdio:"pipe"}).on("close",c=>{c===0?(t.succeed("cursor-agent CLI installed!"),r()):s(new Error("cursor-agent install failed"))})})}catch{t.fail("Could not install cursor-agent CLI"),console.log(e.yellow(` Install manually: curl https://cursor.com/install -fsS | bash
|
|
33
|
+
`))}}}if(o.agent==="codex"&&!n.skipInstall){const t=I("Checking Codex CLI...").start();try{$("codex --version",{encoding:"utf-8",timeout:5e3,stdio:"pipe"}),t.succeed("Codex CLI already installed")}catch{t.text="Installing Codex CLI...";try{await new Promise((r,s)=>{M("npm",["install","-g","@openai/codex"],{stdio:"pipe"}).on("close",c=>{c===0?(t.succeed("Codex CLI installed!"),r()):s(new Error("npm install -g @openai/codex failed"))})})}catch{t.fail("Could not install Codex CLI"),console.log(e.yellow(` Install manually: npm install -g @openai/codex
|
|
34
|
+
`))}}}if(o.agent==="gemini"&&!n.skipInstall){const t=I("Checking Gemini CLI...").start();try{$("gemini --version",{encoding:"utf-8",timeout:5e3,stdio:"pipe"}),t.succeed("Gemini CLI already installed")}catch{t.text="Installing Gemini CLI...";try{await new Promise((s,m)=>{M("npm",["install","-g","@google/gemini-cli"],{stdio:"pipe"}).on("close",h=>{h===0?(t.succeed("Gemini CLI installed!"),s()):m(new Error("npm install -g @google/gemini-cli failed"))})})}catch{t.fail("Could not install Gemini CLI"),console.log(e.yellow(` Install manually: npm install -g @google/gemini-cli
|
|
35
|
+
`))}}const r=I("Configuring Gemini MCP servers...").start();try{const s=i(P(),".gemini"),m=i(s,"settings.json");y(s)||await A(s,{recursive:!0});let c={mcpServers:{}};if(y(m))try{const S=await w(m,"utf-8");c=JSON.parse(S),c.mcpServers||(c.mcpServers={})}catch{}let h;try{const{createRequire:S}=await import("module");h=S(import.meta.url).resolve("@zibby/mcp-browser/bin/mcp-browser-zibby.js")}catch{h="@playwright/mcp"}const z=o.browserMode!=="headless",Y=h==="@playwright/mcp"?["-y","@playwright/mcp","--isolated","--save-video=1280x720","--viewport-size=1280x720","--output-dir","test-results"]:[h,"--isolated","--save-video=1280x720","--viewport-size=1280x720","--output-dir=test-results"];z||Y.push("--headless"),c.mcpServers["playwright-official"]={command:h==="@playwright/mcp"?"npx":"node",args:Y},await u(m,`${JSON.stringify(c,null,2)}
|
|
36
|
+
`,"utf-8");let k="Gemini MCP configured";z?k+=" (headed mode - visible browser)":k+=" (headless mode - hidden browser)",r.succeed(k)}catch(s){r.fail("MCP setup failed"),console.log(e.yellow(" You may need to configure ~/.gemini/settings.json manually")),console.log(e.gray(` Error: ${s.message}
|
|
37
|
+
`))}}if(o.agent==="cursor"&&o.setupMcp){const t=I("Setting up Playwright MCP...").start();try{const{setupPlaywrightMcpCommand:r}=await import("./setup-scripts.js"),s=o.cloudSync||!1,m=o.browserMode!=="headless";await r({headed:m,cloudSync:s,video:"on",viewport:{width:1280,height:720}});let c="Playwright MCP configured";m?c+=" (headed mode - visible browser)":c+=" (headless mode - hidden browser)",s&&(c+=" + Zibby MCP (cloud sync)"),t.succeed(c),s&&!(o.apiKey&&o.apiKey.trim())&&console.log(e.gray(`
|
|
37
38
|
Copy .env.example to .env and set ZIBBY_API_KEY to enable uploads
|
|
38
39
|
`))}catch(r){t.fail("MCP setup script failed"),console.log(e.yellow(" Check if MCP is already configured:")),console.log(e.gray(' \u2022 Open Cursor settings \u2192 Check "playwright-official" MCP')),console.log(e.gray(" \u2022 Run: cursor-agent mcp list")),console.log(e.gray(` \u2022 Or run manually: zibby setup-playwright
|
|
39
40
|
`)),console.log(e.gray(` Error: ${r.message}
|
|
@@ -41,10 +42,10 @@ Using mem0 backend requires mem0ai in your project dependencies.`)),console.log(
|
|
|
41
42
|
\u{1F389} All set!
|
|
42
43
|
`)),console.log(e.cyan("Start the Zibby Chat Agent:")),f&&console.log(e.white(` cd ${l}`)),console.log(e.white(` zibby
|
|
43
44
|
`)),console.log(e.cyan("Or run a test directly:")),console.log(e.white(` zibby run test-specs/examples/example-domain.txt
|
|
44
|
-
`)),console.log(e.cyan("Next steps:"));let
|
|
45
|
+
`)),console.log(e.cyan("Next steps:"));let _=1;console.log(e.white(` ${_++}. cp .env.example .env ${e.gray("# then add your API keys")}`)),g&&console.log(e.white(` ${_++}. Set ${e.bold("ZIBBY_MEMORY_BACKEND")} in .env ${e.gray(`# currently: ${d}`)}`)),console.log(e.white(` ${_++}. Type ${e.bold("zibby")} to chat with the AI testing assistant`)),console.log(e.white(` ${_++}. Write test specs in test-specs/`)),console.log(e.white(` ${_++}. Run: npx zibby run <spec-file>
|
|
45
46
|
`))}catch(b){p.fail("Failed to create project"),console.error(e.red(`
|
|
46
47
|
\u274C Error: ${b.message}
|
|
47
|
-
`)),process.exit(1)}}function F(l,
|
|
48
|
+
`)),process.exit(1)}}function F(l,n={},g={}){const d=["dolt","mem0"].includes(String(g.memoryBackend||"").toLowerCase())?String(g.memoryBackend).toLowerCase():"dolt",a={claude:`
|
|
48
49
|
claude: {
|
|
49
50
|
model: 'auto', // Options: 'auto', 'sonnet-4.6', 'opus-4.6', 'sonnet-4.5', 'opus-4.5'
|
|
50
51
|
maxTokens: 4096,
|
|
@@ -57,12 +58,12 @@ Using mem0 backend requires mem0ai in your project dependencies.`)),console.log(
|
|
|
57
58
|
},`,gemini:`
|
|
58
59
|
gemini: {
|
|
59
60
|
model: 'gemini-2.5-pro', // Options: 'auto', 'gemini-2.5-pro', 'gemini-2.5-flash'
|
|
60
|
-
},`},
|
|
61
|
+
},`},E=l.agent,f=Object.entries(a).filter(([o])=>o!==E).map(([,o])=>o.split(`
|
|
61
62
|
`).map(x=>x.trim()?` // ${x.trimStart()}`:x).join(`
|
|
62
63
|
`)).join(`
|
|
63
64
|
`);return`export default {
|
|
64
65
|
// AI agent settings
|
|
65
|
-
agent: {${a[
|
|
66
|
+
agent: {${a[E]}
|
|
66
67
|
${f}
|
|
67
68
|
strictMode: false,
|
|
68
69
|
},
|
|
@@ -126,7 +127,7 @@ ${f}
|
|
|
126
127
|
// Cloud sync - auto-upload test results & videos (requires ZIBBY_API_KEY in .env)
|
|
127
128
|
cloudSync: ${l.cloudSync||!1}
|
|
128
129
|
};
|
|
129
|
-
`}function W(l,
|
|
130
|
+
`}function W(l,n="dolt"){return`# Zibby Test Automation - Environment Variables
|
|
130
131
|
|
|
131
132
|
# AI Provider Keys
|
|
132
133
|
${{claude:"ANTHROPIC_API_KEY=sk-ant-your_key_here",cursor:`# Cursor Agent uses cursor-agent CLI \u2014 no API key needed
|
|
@@ -142,14 +143,14 @@ ${{claude:"ANTHROPIC_API_KEY=sk-ant-your_key_here",cursor:`# Cursor Agent uses c
|
|
|
142
143
|
# ZIBBY_MEMORY_COMPACT_EVERY=1500 # Auto-compact every N runs (0 to disable)
|
|
143
144
|
|
|
144
145
|
# Chat memory backend
|
|
145
|
-
ZIBBY_MEMORY_BACKEND=${
|
|
146
|
-
`}function U(l,
|
|
146
|
+
ZIBBY_MEMORY_BACKEND=${n}
|
|
147
|
+
`}function U(l,n,g="dolt"){return`# Zibby Test Automation - Environment Variables
|
|
147
148
|
|
|
148
149
|
# AI Provider Keys
|
|
149
150
|
${{claude:"ANTHROPIC_API_KEY=sk-ant-your_key_here",cursor:"# Cursor Agent uses cursor-agent CLI \u2014 no API key needed",codex:"OPENAI_API_KEY=sk-your_key_here",gemini:"GEMINI_API_KEY=your_key_here"}[l.agent]||""}
|
|
150
151
|
|
|
151
152
|
# Zibby Cloud Sync
|
|
152
|
-
ZIBBY_API_KEY=${
|
|
153
|
+
ZIBBY_API_KEY=${n}
|
|
153
154
|
|
|
154
155
|
# Test Memory (Dolt DB) - Auto-compaction settings
|
|
155
156
|
# ZIBBY_MEMORY_MAX_RUNS=3000 # Max test runs to keep per spec
|
|
@@ -158,7 +159,7 @@ ZIBBY_API_KEY=${o}
|
|
|
158
159
|
|
|
159
160
|
# Chat memory backend
|
|
160
161
|
ZIBBY_MEMORY_BACKEND=${g}
|
|
161
|
-
`}function V(l,
|
|
162
|
+
`}function V(l,n,g={}){const d={"@zibby/cli":"^0.1.25","@zibby/core":"^0.1.20"};return g.memoryBackend==="mem0"&&(d.mem0ai="^2.4.6"),JSON.stringify({name:l,version:"1.0.0",type:"module",private:!0,scripts:{"test:spec":"zibby run",test:"playwright test","test:headed":"playwright test --headed"},dependencies:d,devDependencies:{"@playwright/test":"^1.49.0",dotenv:"^17.2.3"}},null,2)}function q(){return`# Dependencies
|
|
162
163
|
node_modules/
|
|
163
164
|
|
|
164
165
|
# Test artifacts
|
|
@@ -186,11 +187,11 @@ Thumbs.db
|
|
|
186
187
|
# Zibby memory (Dolt DB synced separately via dolt remote, not git)
|
|
187
188
|
.zibby/memory/
|
|
188
189
|
.zibby/memory-context.md
|
|
189
|
-
`}function X(l="off",
|
|
190
|
+
`}function X(l="off",n=null){return`import { defineConfig} from '@playwright/test';
|
|
190
191
|
|
|
191
192
|
export default defineConfig({
|
|
192
193
|
testDir: './tests',
|
|
193
|
-
${
|
|
194
|
+
${n?` outputDir: '${n}',
|
|
194
195
|
`:` outputDir: 'test-results/playwright', // Keep Playwright artifacts separate from Zibby workflow output
|
|
195
196
|
`} timeout: 30000,
|
|
196
197
|
retries: 0,
|
|
@@ -256,7 +257,7 @@ Required output format:
|
|
|
256
257
|
4. Expected result
|
|
257
258
|
|
|
258
259
|
Keep the response actionable and short.
|
|
259
|
-
`}function ee(l,
|
|
260
|
+
`}function ee(l,n){return`# ${l}
|
|
260
261
|
|
|
261
262
|
AI-powered test automation with Zibby.
|
|
262
263
|
|
|
@@ -270,7 +271,7 @@ npm install
|
|
|
270
271
|
2. Configure environment:
|
|
271
272
|
\`\`\`bash
|
|
272
273
|
cp .env.example .env
|
|
273
|
-
# Edit .env and add your ${{claude:"ANTHROPIC_API_KEY",codex:"OPENAI_API_KEY",gemini:"GEMINI_API_KEY",cursor:"CURSOR_API_KEY (optional)"}[
|
|
274
|
+
# Edit .env and add your ${{claude:"ANTHROPIC_API_KEY",codex:"OPENAI_API_KEY",gemini:"GEMINI_API_KEY",cursor:"CURSOR_API_KEY (optional)"}[n.agent]}
|
|
274
275
|
\`\`\`
|
|
275
276
|
|
|
276
277
|
3. Run example test:
|
|
@@ -321,7 +322,7 @@ npx playwright test --ui
|
|
|
321
322
|
Edit \`.zibby.config.mjs\` to customize:
|
|
322
323
|
- Agent settings (model, temperature)
|
|
323
324
|
- Browser settings (headless, viewport)
|
|
324
|
-
- Cloud sync${
|
|
325
|
+
- Cloud sync${n.cloudSync?" (enabled)":" (disabled)"}
|
|
325
326
|
- Self-healing behavior
|
|
326
327
|
|
|
327
328
|
## Project Structure
|
package/dist/commands/studio.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import It from"express";import{createServer as
|
|
3
|
-
`)){if(!k.includes("LISTENING"))continue;const E=k.trim().split(/\s+/);if(!(E[1]||"").endsWith(`:${
|
|
4
|
-
`).filter(Boolean))].map(y=>parseInt(y,10)).filter(y=>!Number.isNaN(y)&&y!==process.pid)}catch{return[]}}async function
|
|
2
|
+
import It from"express";import{createServer as Mt}from"http";import{WebSocketServer as Kt}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 Yt,createWriteStream as Ht,mkdirSync as bt,openSync as Zt,readSync as Vt,closeSync as qt,writeFileSync as H,unlinkSync as Qt}from"fs";import{join as a,resolve as O,dirname as jt,basename as Xt}from"path";import{homedir as te}from"os";import{inspect as ee}from"util";import{fileURLToPath as se,pathToFileURL as ne}from"url";import oe from"dotenv";import{promptAndInstallStudio as re,isStudioInstalled as Pt}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 vt,readLatestLiveFramePayloadSync as wt}from"@zibby/core/utils/live-frame-discovery.js";import{getApiUrl as Tt}from"../config/environments.js";const le=se(import.meta.url),ue=jt(le);function Nt(v){const g=a(v,"video.webm");if(u(g))return g;const y=a(v,"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 w=a(y,I);try{return{p:w,mtime:L(w).mtimeMs}}catch{return{p:w,mtime:0}}}).sort((I,w)=>w.mtime-I.mtime)[0].p}catch{return null}}function At(v){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(`:${v}`))continue;const w=E[E.length-1];/^\d+$/.test(w)&&y.add(parseInt(w,10))}return[...y].filter(k=>k!==process.pid)}catch{return[]}try{const g=Y(`lsof -tiTCP:${v} -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 _t(v){let g=At(v);if(g.length!==0){console.log(`[Studio] Port ${v} 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=At(v);for(const y of g)try{process.kill(y,"SIGKILL")}catch{}await new Promise(y=>setTimeout(y,200))}}async function ke(v={}){const g=It(),y=Mt(g),k=new Kt({server:y}),E=v.port||3847,I=process.cwd(),w=te();process.env.DOTENV_CONFIG_QUIET="true";const kt=process.env.NODE_ENV||"development";[O(I,".env.local"),O(I,`.env.${kt}`),O(I,".env")].forEach(s=>{u(s)&&oe.config({path:s,override:!1})});const R=new Map,D=new Map;let Et=null;const G=96e3,nt="studio-cli.log",U="studio-run.json",Ot=".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 xt(){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(),Et=n}async function Rt(){if(ot){xt();return}if(v.update||!Pt()){if(v.update&&Pt()){console.log(`
|
|
5
|
+
Updating Zibby Studio...
|
|
6
|
+
`);const{installStudio:s}=await import("../utils/studio-installer.js");await s()}else if(!await re()){y.close(),process.exit(0);return}}console.log(`
|
|
5
7
|
\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
8
|
\u2551 Zibby Studio \u2551
|
|
7
9
|
\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
|
-
`),await ie({projectRoot:I}),setTimeout(()=>{y.close(),process.exit(0)},1e3)}function rt(){const s=a(I,".zibby.config.mjs");return u(s)?s:a(
|
|
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(
|
|
10
|
-
`,"utf8")}catch{}}return t.json(i)}catch(e){return t.status(500).json({error:e.message||String(e)})}});function
|
|
10
|
+
`),await ie({projectRoot:I}),setTimeout(()=>{y.close(),process.exit(0)},1e3)}function rt(){const s=a(I,".zibby.config.mjs");return u(s)?s:a(w,".zibby.config.mjs")}function x(){rt();const s=a(I,".zibby","output","sessions");return u(s)?s:a(w,".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 Ct(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 Ft(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 Ft(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 Lt(s,t){const o=C(s)||a(x(),s);try{bt(o,{recursive:!0}),H(a(o,Ot),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(w,".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 N of S)N?.isDirectory?.()&&String(N.name||"").startsWith(".zibby")&&p(a(f,N.name,"output","sessions"))}const T=l.filter(h=>u(h));if(T.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 N=String(S.name||"");if(N===e||N===n||N.startsWith(`${e}_`)||n&&N.startsWith(`${n}_`)){const F=a(h,N);if(c.has(F))continue;c.add(F),T.push(F)}}}if(T.length===0)return null;if(T.length===1)return T[0];const M=h=>{let f=0;u(a(h,"execute_live"))&&(f+=20),vt(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 T.sort((h,f)=>M(f)-M(h)),T[0]}function $t(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=wt(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(w,".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(Tt()).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)}
|
|
11
|
+
`,"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(w,".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(Tt()).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(T=>String(T?.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)}
|
|
12
|
+
`,"utf8")}catch{}}return t.json(i)}catch(e){return t.status(500).json({error:e.message||String(e)})}});function zt(s,t){const e=Array.isArray(t)?t.filter(d=>typeof d=="string"):[],o=process.argv[1]?O(process.argv[1]):"",n=o?Xt(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 T=()=>{if(m&&!m.destroyed)try{m.end()}catch{}m=null},M=(f,S)=>{D.delete(n),T(),p||t.status(f).json(S)},h=()=>{const f=[];return k.clients.forEach(S=>{S.sessionId===n&&f.push(S)}),f};try{const f=zt(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=Ht(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),Ct(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 N="",F="",dt=!1,ft=0;const pt=setInterval(()=>{try{const b=vt(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,T();const j={pending:!1,...b};typeof j.stdout=="string"&&j.stdout.length>G&&(j.stdout=`\u2026(truncated)
|
|
11
13
|
${j.stdout.slice(-G)}`),typeof j.stderr=="string"&&j.stderr.length>G&&(j.stderr=`\u2026(truncated)
|
|
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(
|
|
14
|
+
${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(N+=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:N,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(A=>{try{A.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 A=JSON.parse(_(K,"utf8"));P=A.name||A.task}catch(A){console.error("[Studio API] Failed to read session info:",A.message)}else if(u(gt))try{const A=JSON.parse(_(gt,"utf8"));P=A.name||A.task}catch(A){console.error("[Studio API] Failed to read result:",A.message)}const Wt=[/\[SessionRecorder\] Run name set: (.+)/,/Run name: (.+)/];if(!P)for(const A of Wt){const St=N.match(A);if(St){P=St[1].trim();break}}const Jt=q(r,I),tt=Nt(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:N,stderr:F,runName:P,videoPath:tt&&u(tt)?tt:null,eventsPath:et&&u(et)?et:null,codegenFiles:Jt,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(Lt(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=Zt(l,"r");try{Vt(m,p,0,d,i)}finally{qt(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=wt(n)||$t(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=Nt(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}),Yt(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 T=m||r||"zibby init failed";t.status(500).json({success:!1,error:T,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{}})},Bt=console.log,Dt=console.error,Gt=console.warn,X=s=>s.map(t=>{if(t==null)return String(t);if(typeof t=="object")try{return ee(t,{depth:4,colors:!1,breakLength:100})}catch{return String(t)}return String(t)}).join(" ");console.log=(...s)=>{Bt(...s),Q("info",X(s))},console.error=(...s)=>{Dt(...s),Q("error",X(s))},console.warn=(...s)=>{Gt(...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(`${ne(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 Ut=()=>{ot&&console.log(`
|
|
13
15
|
\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
16
|
\u2551 Zibby Studio \u2551
|
|
15
17
|
\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
|
|
@@ -18,5 +20,5 @@ ${j.stderr.slice(-G)}`),D.set(n,j),h().forEach(P=>{try{P.send(JSON.stringify({ty
|
|
|
18
20
|
Project: ${I}
|
|
19
21
|
|
|
20
22
|
Press Ctrl+C to stop
|
|
21
|
-
`),
|
|
23
|
+
`),v.open!==!1&&Rt().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 _t(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)}),Ut()})}await _t(E),lt();const ut=()=>{console.log(`
|
|
22
24
|
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};
|
|
@@ -1,45 +1,45 @@
|
|
|
1
|
-
import{readFileSync as
|
|
1
|
+
import{readFileSync as _,writeFileSync as j,existsSync as v,mkdirSync as z}from"fs";import{resolve as A,join as y}from"path";import o from"chalk";import k from"ora";import P from"dotenv";import{getApiUrl as b,getCurrentEnvironment as E}from"../config/environments.js";import{validateGraphConfig as B}from"@zibby/core/framework/graph-compiler.js";import{generateWorkflowCode as J,generateNodeConfigsJson as N}from"@zibby/core/framework/code-generator.js";import"@zibby/core/templates/register-nodes.js";P.config();function x(s){const e=s.apiKey||process.env.ZIBBY_API_KEY;e||(console.log(o.red(`
|
|
2
2
|
ZIBBY_API_KEY not set`)),console.log(o.gray(` Add to .env: ZIBBY_API_KEY=zby_xxx
|
|
3
3
|
`)),process.exit(1));const f=s.project||process.env.ZIBBY_PROJECT_ID;return f||(console.log(o.red(`
|
|
4
4
|
--project or ZIBBY_PROJECT_ID is required`)),console.log(o.gray(` Example: zibby workflow download --project <id> --type analysis
|
|
5
|
-
`)),process.exit(1)),{apiKey:e,projectId:f}}const
|
|
6
|
-
--type is required`)),console.log(o.gray(`
|
|
7
|
-
`)),process.exit(1))
|
|
8
|
-
Invalid workflow type: "${e}"`)),console.log(o.gray(`
|
|
9
|
-
`)),process.exit(1)),e}async function
|
|
5
|
+
`)),process.exit(1)),{apiKey:e,projectId:f}}const C=["analysis","implementation","run_test"],U=/^[a-z][a-z0-9_-]{0,62}[a-z0-9]$/;function D(s){const e=s.type;return e||(console.log(o.red(`
|
|
6
|
+
--type is required`)),console.log(o.gray(` Built-in types: ${C.join(", ")}`)),console.log(o.gray(` Custom workflows: any lowercase slug (e.g., ticket-triage)
|
|
7
|
+
`)),process.exit(1)),!C.includes(e)&&!U.test(e)&&(console.log(o.red(`
|
|
8
|
+
Invalid workflow type: "${e}"`)),console.log(o.gray(` Built-in: ${C.join(", ")}`)),console.log(o.gray(` Custom: lowercase letters, digits, hyphens (2\u201364 chars)
|
|
9
|
+
`)),process.exit(1)),e}async function H(s){const e=E(),{apiKey:f,projectId:d}=x(s),n=D(s);console.log(o.bold.cyan(`
|
|
10
10
|
Zibby Workflow Download
|
|
11
|
-
`)),console.log(o.gray(" ".padEnd(52,"-"))),console.log(o.white(` Environment: ${o.cyan(e.name)}`)),console.log(o.white(` Project: ${o.cyan(d)}`)),console.log(o.white(` Type: ${o.cyan(n)}`)),console.log(o.gray(" ".padEnd(52,"-")));const l=
|
|
11
|
+
`)),console.log(o.gray(" ".padEnd(52,"-"))),console.log(o.white(` Environment: ${o.cyan(e.name)}`)),console.log(o.white(` Project: ${o.cyan(d)}`)),console.log(o.white(` Type: ${o.cyan(n)}`)),console.log(o.gray(" ".padEnd(52,"-")));const l=k(" Fetching workflow from cloud...").start();try{const p=b(),r=await fetch(`${p}/projects/${d}/workflows/${n}`,{method:"GET",headers:{"Content-Type":"application/json",Authorization:`Bearer ${f}`}});if(!r.ok){const a=await r.text();l.fail(` API error: ${r.status}`),console.log(o.red(` ${a}
|
|
12
12
|
`)),process.exit(1)}const t=await r.json();!t.graph&&t.isDefault?l.info(" No custom workflow saved -- downloading default graph"):l.succeed(` Fetched workflow (v${t.version||0})`);const i=t.graph||null;if(!i){console.log(o.yellow(`
|
|
13
13
|
No graph config available for this workflow.`)),console.log(o.gray(" The project is using the built-in default graph.")),console.log(o.gray(` Edit the graph in the UI first, or use --include-default to download the default.
|
|
14
14
|
`)),s.includeDefault||process.exit(0),l.start(" Fetching default graph...");const{getDefaultGraph:a}=await import("@zibby/core/templates/graphs/index.js"),w=a(n);return w||(l.fail(` No default graph found for type "${n}"`),process.exit(1)),F(n,{graph:w,version:0,isDefault:!0,projectId:d,workflowType:n},s)}return F(n,{graph:i,version:t.version||0,isDefault:t.isDefault||!1,projectId:d,workflowType:n},s)}catch(p){l.fail(" Download failed"),console.log(o.red(`
|
|
15
15
|
${p.message}
|
|
16
|
-
`)),process.exit(1)}}function F(s,e,f){const d=process.cwd(),n=f.output||y(d,".zibby");
|
|
17
|
-
`,"utf-8");const
|
|
16
|
+
`)),process.exit(1)}}function F(s,e,f){const d=process.cwd(),n=f.output||y(d,".zibby");v(n)||z(n,{recursive:!0});const l={projectId:e.projectId,workflowType:e.workflowType,version:e.version,isDefault:e.isDefault},p=`workflow-${s}.js`,r=y(n,p),t=J(e.graph,l);j(r,t,"utf-8");const i=e.graph.nodeConfigs||{},a=N(i),w=`workflow-${s}.config.json`,m=y(n,w);j(m,`${JSON.stringify(a,null,2)}
|
|
17
|
+
`,"utf-8");const $=`workflow-${s}.json`,c=y(n,$),g={_meta:{...l,downloadedAt:new Date().toISOString()},...e.graph};j(c,`${JSON.stringify(g,null,2)}
|
|
18
18
|
`,"utf-8"),console.log(o.green(`
|
|
19
|
-
Generated workflow files:`)),console.log(o.white(` ${o.bold(r)}`)),console.log(o.gray(" Executable graph with inline tool bindings")),console.log(o.white(` ${o.bold(
|
|
19
|
+
Generated workflow files:`)),console.log(o.white(` ${o.bold(r)}`)),console.log(o.gray(" Executable graph with inline tool bindings")),console.log(o.white(` ${o.bold(m)}`)),console.log(o.gray(" Extra prompt instructions & runtime config")),console.log(o.white(` ${o.bold(c)}`)),console.log(o.gray(" Raw JSON config (for upload back to cloud)")),console.log(""),console.log(o.gray(` Version: ${e.version}`)),console.log(o.gray(` Nodes: ${e.graph.nodes?.length||0}`)),console.log(o.gray(` Edges: ${e.graph.edges?.length||0}
|
|
20
20
|
`)),console.log(o.white(" To run locally:")),console.log(o.cyan(` zibby analyze --workflow ${r}
|
|
21
21
|
`)),console.log(o.white(" To upload changes back:")),console.log(o.cyan(` zibby workflow upload --project ${e.projectId} --type ${s}
|
|
22
|
-
`))}async function
|
|
22
|
+
`))}async function M(s){const e=E(),{apiKey:f,projectId:d}=x(s),n=D(s);console.log(o.bold.cyan(`
|
|
23
23
|
Zibby Workflow Upload
|
|
24
|
-
`)),console.log(o.gray(" ".padEnd(52,"-"))),console.log(o.white(` Environment: ${o.cyan(e.name)}`)),console.log(o.white(` Project: ${o.cyan(d)}`)),console.log(o.white(` Type: ${o.cyan(n)}`)),console.log(o.gray(" ".padEnd(52,"-")));const l=process.cwd(),p=y(l,".zibby",`workflow-${n}.json`),r=y(l,".zibby",`workflow-${n}.js`),t=s.file||(
|
|
24
|
+
`)),console.log(o.gray(" ".padEnd(52,"-"))),console.log(o.white(` Environment: ${o.cyan(e.name)}`)),console.log(o.white(` Project: ${o.cyan(d)}`)),console.log(o.white(` Type: ${o.cyan(n)}`)),console.log(o.gray(" ".padEnd(52,"-")));const l=process.cwd(),p=y(l,".zibby",`workflow-${n}.json`),r=y(l,".zibby",`workflow-${n}.js`),t=s.file||(v(r)?r:p);v(t)||(console.log(o.red(`
|
|
25
25
|
File not found: ${t}`)),console.log(o.gray(` Download a workflow first: zibby workflow download --project <id> --type <type>
|
|
26
|
-
`)),process.exit(1));const i=t.endsWith(".js")||t.endsWith(".mjs");let a;if(i){const c=
|
|
26
|
+
`)),process.exit(1));const i=t.endsWith(".js")||t.endsWith(".mjs");let a;if(i){const c=k(" Loading JS workflow module...").start();try{const{pathToFileURL:g}=await import("url"),u=await import(g(A(t)).href),h=u.buildGraph();a=h.serialize();const I=u.nodeConfigs||{};if(Object.keys(I).length>0)for(const[S,T]of Object.entries(I))a.nodeConfigs[S]={...T,...a.nodeConfigs[S]};c.succeed(` Loaded JS module (${h.nodes.size} nodes)`)}catch(g){c.fail(" Failed to load JS module"),console.log(o.red(`
|
|
27
27
|
${g.message}
|
|
28
|
-
`)),process.exit(1)}}else{let c;try{c=JSON.parse(
|
|
28
|
+
`)),process.exit(1)}}else{let c;try{c=JSON.parse(_(t,"utf-8"))}catch(h){console.log(o.red(`
|
|
29
29
|
Failed to parse ${t}: ${h.message}
|
|
30
30
|
`)),process.exit(1)}const{_meta:g,...u}=c;a=u}(!a.nodes||!a.edges)&&(console.log(o.red(`
|
|
31
31
|
Invalid workflow file: missing nodes or edges`)),console.log(o.gray(` The file should contain { nodes: [...], edges: [...], nodeConfigs: {...} }
|
|
32
32
|
`)),process.exit(1)),console.log(o.gray(`
|
|
33
|
-
File: ${t}`)),console.log(o.gray(` Format: ${i?"JavaScript (serialized via graph.serialize())":"JSON"}`)),console.log(o.gray(` Nodes: ${a.nodes.length}`)),console.log(o.gray(` Edges: ${a.edges.length}`));const w=
|
|
33
|
+
File: ${t}`)),console.log(o.gray(` Format: ${i?"JavaScript (serialized via graph.serialize())":"JSON"}`)),console.log(o.gray(` Nodes: ${a.nodes.length}`)),console.log(o.gray(` Edges: ${a.edges.length}`));const w=k(" Validating graph...").start(),m=B(a);if(!m.valid){w.fail(" Graph validation failed"),console.log("");for(const c of m.errors)console.log(o.red(` ${c}`));console.log(o.gray(`
|
|
34
34
|
Fix the errors above and try again.
|
|
35
|
-
`)),process.exit(1)}w.succeed(" Graph is valid");const
|
|
36
|
-
`)),process.exit(1)}const u=await g.json()
|
|
35
|
+
`)),process.exit(1)}w.succeed(" Graph is valid");const $=k(" Uploading to cloud...").start();try{const c=b(),g=await fetch(`${c}/projects/${d}/workflows/${n}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${f}`},body:JSON.stringify({graph:a})});if(!g.ok){const h=await g.text();$.fail(` API error: ${g.status}`),console.log(o.red(` ${h}
|
|
36
|
+
`)),process.exit(1)}const u=await g.json();$.succeed(` Uploaded successfully (v${u.version})`),console.log(o.green(`
|
|
37
37
|
Workflow "${n}" updated to version ${u.version}`)),console.log(o.gray(` Project: ${d}
|
|
38
|
-
`))}catch(c){
|
|
38
|
+
`))}catch(c){$.fail(" Upload failed"),console.log(o.red(`
|
|
39
39
|
${c.message}
|
|
40
|
-
`)),process.exit(1)}}async function
|
|
40
|
+
`)),process.exit(1)}}const O=["analysis","implementation","run_test"];async function Q(s){const e=E(),{apiKey:f,projectId:d}=x(s);console.log(o.bold.cyan(`
|
|
41
41
|
Zibby Workflows
|
|
42
|
-
`));const n=
|
|
42
|
+
`));const n=k(" Fetching workflows...").start();try{const l=b(),p=[];for(const r of O){const t=await fetch(`${l}/projects/${d}/workflows/${r}`,{method:"GET",headers:{"Content-Type":"application/json",Authorization:`Bearer ${f}`}});if(t.ok){const i=await t.json();p.push({type:r,version:i.version||0,isDefault:i.isDefault!==!1&&!i.graph,nodes:i.graph?.nodes?.length||0,updatedAt:i.updatedAt||null})}}n.succeed(` Fetched workflows
|
|
43
43
|
`),console.log(o.gray(" ".padEnd(70,"-"))),console.log(o.white(" Type".padEnd(20))+o.white("Version".padEnd(10))+o.white("Nodes".padEnd(10))+o.white("Status".padEnd(15))+o.white("Updated")),console.log(o.gray(" ".padEnd(70,"-")));for(const r of p){const t=r.isDefault?o.gray("default"):o.green("custom"),i=r.updatedAt?new Date(r.updatedAt).toLocaleDateString():o.gray("-");console.log(` ${o.cyan(r.type.padEnd(18))}${String(r.version).padEnd(10)}${String(r.nodes).padEnd(10)}${t.padEnd(15)}${i}`)}console.log(o.gray(" ".padEnd(70,"-"))),console.log("")}catch(l){n.fail(" Failed to fetch workflows"),console.log(o.red(`
|
|
44
44
|
${l.message}
|
|
45
|
-
`)),process.exit(1)}}export{
|
|
45
|
+
`)),process.exit(1)}}export{H as workflowDownloadCommand,Q as workflowListCommand,M as workflowUploadCommand};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import{existsSync as b}from"fs";import{readFile as P,readdir as I}from"fs/promises";import{join as y,resolve as C,relative as E}from"path";import{pathToFileURL as z}from"url";import o from"chalk";import u from"ora";import A from"dotenv";import{getApiUrl as T,getCurrentEnvironment as _}from"../../config/environments.js";import{validateGraphConfig as S}from"@zibby/core/framework/graph-compiler.js";A.config();function O(r){const t=r.apiKey||process.env.ZIBBY_API_KEY;t||(console.log(o.red(`
|
|
2
|
+
ZIBBY_API_KEY not set`)),console.log(o.gray(` Add to .env: ZIBBY_API_KEY=zby_xxx
|
|
3
|
+
`)),process.exit(1));const e=r.project||process.env.ZIBBY_PROJECT_ID;return e||(console.log(o.red(`
|
|
4
|
+
--project or ZIBBY_PROJECT_ID is required`)),console.log(o.gray(` Example: zibby deploy my-workflow --project <id>
|
|
5
|
+
`)),process.exit(1)),{apiKey:t,projectId:e}}function Y(r){const t=process.cwd();return y(t,".zibby","workflows",r)}async function K(r,t){const e=y(r,"workflow.json");if(!b(e))return{name:t,triggers:{api:!0}};const l=await P(e,"utf-8");return JSON.parse(l)}async function U(r){const t={};async function e(l){const w=await I(l,{withFileTypes:!0});for(const g of w){const i=y(l,g.name);if(g.isDirectory())await e(i);else if(/\.(mjs|js|json)$/.test(g.name)){const f=E(r,i);t[f]=await P(i,"utf-8")}}}return await e(r),t}async function q(r,t){r||(console.log(o.red(`
|
|
6
|
+
Workflow name is required`)),console.log(o.gray(" Usage: zibby deploy <workflow-name>")),console.log(o.gray(` Example: zibby deploy ticket-triage
|
|
7
|
+
`)),process.exit(1));const e=r.toLowerCase(),l=Y(e);b(l)||(console.log(o.red(`
|
|
8
|
+
Workflow not found: .zibby/workflows/${e}/`)),console.log(o.gray(" Create one first:")),console.log(o.cyan(` zibby g workflow ${e}
|
|
9
|
+
`)),process.exit(1));const w=_(),{apiKey:g,projectId:i}=O(t);console.log(o.bold.cyan(`
|
|
10
|
+
Zibby Workflow Deploy
|
|
11
|
+
`)),console.log(o.gray(" ".padEnd(56,"-"))),console.log(o.white(` Environment: ${o.cyan(w.name)}`)),console.log(o.white(` Project: ${o.cyan(i)}`)),console.log(o.white(` Workflow: ${o.cyan(e)}`)),console.log(o.gray(" ".padEnd(56,"-")));const f=u(" Loading workflow module...").start();let m,$;try{$=await K(l,e);const n=y(l,"graph.mjs");if(!b(n))throw new Error(`graph.mjs not found in .zibby/workflows/${e}/`);const s=await import(z(C(n)).href),a=$.entryClass,c=a&&s[a]||s.default||Object.values(s).find(B=>typeof B=="function"&&B.prototype?.buildGraph);if(!c)throw new Error("No WorkflowAgent class found in graph.mjs");const x=new c().buildGraph();m=x.serialize(),f.succeed(` Loaded ${c.name} (${x.nodes.size} nodes)`)}catch(n){f.fail(" Failed to load workflow"),console.log(o.red(`
|
|
12
|
+
${n.message}
|
|
13
|
+
`)),process.exit(1)}const k=u(" Bundling source files...").start();let d;try{d=await U(l);const n=Object.keys(d).length,s=(Buffer.byteLength(JSON.stringify(d),"utf-8")/1024).toFixed(1);k.succeed(` Bundled ${n} files (${s} KB)`)}catch(n){k.fail(" Failed to bundle sources"),console.log(o.red(`
|
|
14
|
+
${n.message}
|
|
15
|
+
`)),process.exit(1)}const v=u(" Validating graph...").start(),j=S(m);if(!j.valid){v.fail(" Graph validation failed");for(const n of j.errors)console.log(o.red(` ${n}`));console.log(o.gray(`
|
|
16
|
+
Fix the errors above and try again.
|
|
17
|
+
`)),process.exit(1)}v.succeed(" Graph is valid");const h=u(" Deploying to cloud...").start();try{const n=T(),s=await fetch(`${n}/projects/${i}/workflows/${e}`,{method:"PUT",headers:{"Content-Type":"application/json",Authorization:`Bearer ${g}`},body:JSON.stringify({graph:m,sources:d})});if(!s.ok){const p=await s.text();h.fail(` API error: ${s.status}`),console.log(o.red(` ${p}
|
|
18
|
+
`)),process.exit(1)}const a=await s.json();h.succeed(` Deployed successfully (v${a.version})`);const c=`${n}/projects/${i}/workflows/${e}/trigger`;if(console.log(o.green(`
|
|
19
|
+
Workflow "${e}" deployed to version ${a.version}`)),console.log(o.white(`
|
|
20
|
+
Trigger URL (API):`)),console.log(o.cyan(` POST ${c}`)),a.subdomain){const p=`https://${a.subdomain}.workflows.zibby.app`;console.log(o.white(`
|
|
21
|
+
Trigger URL (subdomain):`)),console.log(o.cyan(` POST ${p}`))}console.log(o.white(`
|
|
22
|
+
Test with:`)),console.log(o.gray(` curl -X POST ${c} \\`)),console.log(o.gray(' -H "Authorization: Bearer $ZIBBY_API_KEY" \\')),console.log(o.gray(' -H "Content-Type: application/json" \\')),console.log(o.gray(` -d '{"input": {"key": "value"}}'
|
|
23
|
+
`))}catch(n){h.fail(" Deploy failed"),console.log(o.red(`
|
|
24
|
+
${n.message}
|
|
25
|
+
`)),process.exit(1)}}export{q as deployWorkflowCommand};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import{mkdir as d,writeFile as a}from"fs/promises";import{existsSync as u}from"fs";import{join as t}from"path";import o from"chalk";import f from"ora";const h=/^[a-z][a-z0-9-]{0,62}[a-z0-9]$/,c=["stellar","quantum","cosmic","nova","nebula","solar","lunar","atomic","plasma","fusion","pulse","flux","spark","blaze","ember","radiant","luminous","electric","magnetic","kinetic","neon","cyber","pixel","matrix","vector","synth","neural","prism","zenith","phoenix","catalyst","nexus","echo","wave","crystal","jade","ruby","emerald","onyx","amber","silver","turbo","lightning","thunder","storm","arcane","mystic","ethereal","celestial","swift","crimson","iron","cobalt"],m=["flow","runner","pipeline","stream","circuit","engine","beacon","forge","relay","shuttle","conduit","gateway","sentinel","scout","pilot","voyager","ranger","dispatch","signal","pulse","agent","daemon","spark","orbit","vector","nexus","matrix","grid","mesh","bridge","link","node","craft","bolt","ray","arc","wave","hook","probe","shard"];function y(){const r=c[Math.floor(Math.random()*c.length)],e=m[Math.floor(Math.random()*m.length)];return`${r}-${e}`}function w(r){return`${r.split("-").map(e=>e.charAt(0).toUpperCase()+e.slice(1)).join("")}Workflow`}function x(r,e){return`/**
|
|
2
|
+
* ${r}
|
|
3
|
+
*
|
|
4
|
+
* buildGraph() \u2014 define nodes, edges, conditional routing
|
|
5
|
+
* onComplete(result) \u2014 post-processing after the graph finishes
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { WorkflowAgent, WorkflowGraph } from '@zibby/core';
|
|
9
|
+
import { exampleNode } from './nodes/index.mjs';
|
|
10
|
+
|
|
11
|
+
export class ${r} extends WorkflowAgent {
|
|
12
|
+
buildGraph() {
|
|
13
|
+
const graph = new WorkflowGraph();
|
|
14
|
+
|
|
15
|
+
graph.addNode('example', exampleNode);
|
|
16
|
+
|
|
17
|
+
graph.setEntryPoint('example');
|
|
18
|
+
graph.addEdge('example', 'END');
|
|
19
|
+
|
|
20
|
+
return graph;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async onComplete(result) {
|
|
24
|
+
console.log(\`[${e}] workflow complete \u2014 success: \${result.success !== false}\`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
`}function b(){return`export { exampleNode } from './example.mjs';
|
|
28
|
+
`}function k(){return`import { z } from '@zibby/core';
|
|
29
|
+
|
|
30
|
+
const ExampleOutputSchema = z.object({
|
|
31
|
+
summary: z.string().describe('A short summary of the result'),
|
|
32
|
+
status: z.enum(['ok', 'warn', 'error']).describe('Overall status'),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const exampleNode = {
|
|
36
|
+
name: 'example',
|
|
37
|
+
|
|
38
|
+
prompt: (state) => \`You are a helpful workflow node.
|
|
39
|
+
|
|
40
|
+
Input:
|
|
41
|
+
\${JSON.stringify(state.input || {}, null, 2)}
|
|
42
|
+
|
|
43
|
+
Analyze the input and return a summary with a status.\`,
|
|
44
|
+
|
|
45
|
+
outputSchema: ExampleOutputSchema,
|
|
46
|
+
};
|
|
47
|
+
`}function $(r,e){return`${JSON.stringify({name:r,description:`${e} workflow`,entryClass:e,triggers:{api:!0}},null,2)}
|
|
48
|
+
`}async function W(r){let e;r?e=r.toLowerCase():(e=y(),console.log(o.gray(`
|
|
49
|
+
No name provided \u2014 generated: ${o.white(e)}`))),h.test(e)||(console.log(o.red(`
|
|
50
|
+
Invalid workflow name: "${r}"`)),console.log(o.gray(" Must be lowercase, start with a letter, use only a-z, 0-9, hyphens")),console.log(o.gray(" Length: 2\u201364 characters")),console.log(o.gray(` Example: ticket-triage, pr-review, deploy-checker
|
|
51
|
+
`)),process.exit(1));const p=process.cwd(),n=t(p,".zibby","workflows",e),s=t(n,"nodes");u(n)&&(console.log(o.red(`
|
|
52
|
+
Workflow already exists: .zibby/workflows/${e}/`)),console.log(o.gray(` Choose a different name or delete the existing folder.
|
|
53
|
+
`)),process.exit(1));const l=w(e),i=f(` Scaffolding workflow "${e}"...`).start();try{await d(s,{recursive:!0}),await Promise.all([a(t(n,"graph.mjs"),x(l,e)),a(t(s,"index.mjs"),b()),a(t(s,"example.mjs"),k()),a(t(n,"workflow.json"),$(e,l))]),i.succeed(` Scaffolded ${o.bold(e)}`),console.log(o.green(`
|
|
54
|
+
Created:`)),console.log(o.white(` .zibby/workflows/${e}/`)),console.log(o.gray(` graph.mjs ${l} (entry)`)),console.log(o.gray(" nodes/index.mjs barrel export")),console.log(o.gray(" nodes/example.mjs starter node (prompt + schema)")),console.log(o.gray(" workflow.json manifest")),console.log(o.white(`
|
|
55
|
+
Next steps:`)),console.log(o.cyan(` 1. Edit nodes in .zibby/workflows/${e}/nodes/`)),console.log(o.cyan(" 2. Wire them in graph.mjs")),console.log(o.cyan(" 3. Test locally:")),console.log(o.cyan(` zibby start ${e}`)),console.log(o.cyan(" 4. Deploy to cloud:")),console.log(o.cyan(` zibby deploy ${e}
|
|
56
|
+
`))}catch(g){i.fail(" Scaffold failed"),console.log(o.red(`
|
|
57
|
+
${g.message}
|
|
58
|
+
`)),process.exit(1)}}export{W as generateWorkflowCommand};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{mkdirSync as S,writeFileSync as C,existsSync as k}from"fs";import{join as w,dirname as b,resolve as W}from"path";import{pathToFileURL as j}from"url";const $=process.env.WORKSPACE||"/workspace";async function F(){const s=process.env.WORKFLOW_SOURCES_URL;if(!s)throw new Error("WORKFLOW_SOURCES_URL env var is required");console.log("\u{1F4E6} Fetching workflow sources via pre-signed URL\u2026");const o=await fetch(s);if(!o.ok)throw new Error(`Failed to fetch sources: ${o.status} ${o.statusText}`);const e=await o.json();if(!e.sources||typeof e.sources!="object")throw new Error('Invalid sources payload \u2014 missing "sources" map');return e}function L(s,o){const e=W(o);let t=0;for(const[r,c]of Object.entries(s)){const n=W(o,r);if(!n.startsWith(`${e}/`)&&n!==e){console.error(` \u26D4 Skipping unsafe path: ${r}`);continue}S(b(n),{recursive:!0}),C(n,c,"utf-8"),t++}return t}async function P(s,o){const e=w(s,"graph.mjs");if(!k(e))throw new Error(`graph.mjs not found at ${e}`);const t=await import(j(e).href),r=o?.entryClass,c=r&&t[r]||t.default||Object.values(t).find(n=>typeof n=="function"&&n.prototype?.buildGraph);if(!c)throw new Error("No WorkflowAgent class found in graph.mjs");return c}async function K(){const{WORKFLOW_JOB_ID:s,WORKFLOW_TYPE:o,PROJECT_ID:e,AGENT_TYPE:t,MODEL:r}=process.env;o||(console.error("\u274C Missing WORKFLOW_TYPE env var"),process.exit(1)),console.log(`
|
|
3
|
+
\u{1F680} Zibby Custom Workflow Runner`),console.log(` Job: ${s||"local"}`),console.log(` Workflow: ${o}`),console.log(` Project: ${e||"none"}`),console.log(` Agent: ${t||"default"}`),console.log(` Model: ${r||"auto"}`),console.log("\u2500".repeat(60));const c=await F(),{sources:n,input:O,workflowType:l,version:y}=c;console.log(` Workflow v${y||"?"} \u2014 ${Object.keys(n).length} source files`);const a=w($,".zibby","workflows",l||o),R=L(n,a);console.log(` \u2705 Wrote ${R} files to ${a}`);let p={};const d=w(a,"workflow.json");if(k(d)){const{readFileSync:f}=await import("fs");p=JSON.parse(f(d,"utf-8"))}const g=await P(a,p);console.log(` \u2705 Loaded ${g.name}`);const v=Date.now(),i=new g({workflow:l||o}),h=i.buildGraph(),E={input:O||{},cwd:$,runId:s||`run-${Date.now()}`};console.log(`
|
|
4
|
+
\u25B6 Running graph (${h.nodes?.size||"?"} nodes)\u2026
|
|
5
|
+
`);let u;try{u=await h.run(i,E)}catch(f){console.error(`
|
|
6
|
+
\u274C Workflow execution failed: ${f.message}`),console.error(f.stack),process.exit(1)}const m=((Date.now()-v)/1e3).toFixed(1);u?.success!==!1?console.log(`
|
|
7
|
+
\u2705 Workflow "${l||o}" completed in ${m}s`):(console.error(`
|
|
8
|
+
\u274C Workflow "${l||o}" failed after ${m}s`),process.exit(1)),i.onComplete&&await i.onComplete(u)}export{K as runWorkflowCommand};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import{existsSync as y}from"fs";import{readFile as j}from"fs/promises";import{join as m}from"path";import{pathToFileURL as E}from"url";import o from"chalk";import T from"ora";const W=3848;function S(e){const n=process.cwd();return m(n,".zibby","workflows",e)}async function z(e,n){const t=m(e,"graph.mjs");if(!y(t))throw new Error(`graph.mjs not found in .zibby/workflows/${n}/`);const l=await O(e,n),a=await import(E(t).href),s=l.entryClass,i=s&&a[s]||a.default||Object.values(a).find(r=>typeof r=="function"&&r.prototype?.buildGraph);if(!i)throw new Error("No WorkflowAgent class found in graph.mjs. Export a class with buildGraph() method.");return{AgentClass:i,manifest:l}}async function O(e,n){const t=m(e,"workflow.json");if(!y(t))return{name:n,triggers:{api:!0}};const l=await j(t,"utf-8");return JSON.parse(l)}async function A(e,n){e||(console.log(o.red(`
|
|
2
|
+
Workflow name is required`)),console.log(o.gray(" Usage: zibby start <workflow-name>")),console.log(o.gray(` Example: zibby start ticket-triage
|
|
3
|
+
`)),process.exit(1));const t=e.toLowerCase(),l=S(t);y(l)||(console.log(o.red(`
|
|
4
|
+
Workflow not found: .zibby/workflows/${t}/`)),console.log(o.gray(" Create one first:")),console.log(o.cyan(` zibby g workflow ${t}
|
|
5
|
+
`)),process.exit(1));const a=T(` Loading workflow "${t}"...`).start();let s,i;try{({AgentClass:s,manifest:i}=await z(l,t)),a.succeed(` Loaded ${o.bold(i.entryClass||s.name)} (${t})`)}catch(g){a.fail(" Failed to load workflow"),console.log(o.red(`
|
|
6
|
+
${g.message}
|
|
7
|
+
`)),process.exit(1)}const r=parseInt(n.port,10)||W;let f;try{f=(await import("express")).default}catch{console.log(o.red(`
|
|
8
|
+
express is required for local workflow server`)),console.log(o.gray(` npm install express
|
|
9
|
+
`)),process.exit(1)}const w=f();w.use(f.json({limit:"1mb"})),w.get("/health",(g,d)=>{d.json({status:"ok",workflow:t,class:s.name})}),w.post("/trigger",async(g,d)=>{const c=`local-${Date.now()}`,h=g.body.input||g.body||{};console.log(o.cyan(`
|
|
10
|
+
\u25B6 Run ${c} triggered`)),console.log(o.gray(` input: ${JSON.stringify(h).slice(0,200)}`)),d.status(202).json({runId:c,status:"running",workflow:t});try{const u=Date.now(),p=new s({workflow:t}),$=p.buildGraph(),C={input:h,cwd:process.cwd(),runId:c},k=await $.run(p,C),b=((Date.now()-u)/1e3).toFixed(1),x=k?.success!==!1;console.log(x?o.green(` \u2714 Run ${c} succeeded (${b}s)`):o.red(` \u2716 Run ${c} failed (${b}s)`)),p.onComplete&&await p.onComplete(k)}catch(u){console.log(o.red(` \u2716 Run ${c} error: ${u.message}`))}}),w.listen(r,()=>{console.log(o.bold.cyan(`
|
|
11
|
+
Zibby Workflow Server \u2014 ${t}
|
|
12
|
+
`)),console.log(o.gray(" ".padEnd(56,"-"))),console.log(o.white(` Workflow: ${o.cyan(t)}`)),console.log(o.white(` Class: ${o.cyan(s.name)}`)),console.log(o.white(` Port: ${o.cyan(r)}`)),console.log(o.gray(" ".padEnd(56,"-"))),console.log(o.white(`
|
|
13
|
+
Endpoints:`)),console.log(o.gray(` GET http://localhost:${r}/health`)),console.log(o.cyan(` POST http://localhost:${r}/trigger`)),console.log(o.white(`
|
|
14
|
+
Test with:`)),console.log(o.gray(` curl -X POST http://localhost:${r}/trigger \\`)),console.log(o.gray(' -H "Content-Type: application/json" \\')),console.log(o.gray(` -d '{"input": {"key": "value"}}'
|
|
15
|
+
`)),console.log(o.gray(` Ctrl+C to stop
|
|
16
|
+
`))})}export{A as startWorkflowCommand};
|
package/dist/package.json
CHANGED