@zibby/cli 0.1.26 → 0.1.27
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/README.md +44 -44
- package/dist/auth/cli-login.js +18 -0
- package/dist/bin/zibby.js +2 -0
- package/dist/commands/agent-reliability.js +8 -0
- package/dist/commands/analyze-graph.js +18 -0
- package/dist/commands/chat-session-store.js +1 -0
- package/dist/commands/chat.js +79 -0
- package/dist/commands/ci-setup.js +18 -0
- package/dist/commands/generate.js +69 -0
- package/dist/commands/implement.js +65 -0
- package/dist/commands/init.js +344 -0
- package/dist/commands/list-projects.js +11 -0
- package/dist/commands/memory.js +39 -0
- package/dist/commands/run-capacity-queue-cli.js +4 -0
- package/dist/commands/run.js +112 -0
- package/dist/commands/setup-scripts.js +15 -0
- package/dist/commands/studio.js +33 -0
- package/dist/commands/upload.js +22 -0
- package/dist/commands/video.js +6 -0
- package/dist/commands/workflow.js +45 -0
- package/dist/config/config.js +1 -0
- package/dist/config/environments.js +1 -0
- package/dist/utils/chat-run-lifecycle.js +3 -0
- package/dist/utils/execution-context.js +1 -0
- package/dist/utils/progress-reporter.js +1 -0
- package/dist/utils/studio-cli-log-mirror.js +1 -0
- package/dist/utils/studio-installer.js +7 -0
- package/dist/utils/studio-launcher.js +1 -0
- package/package.json +19 -16
- package/bin/zibby.js +0 -273
- package/src/auth/cli-login.js +0 -404
- package/src/commands/analyze-graph.js +0 -336
- package/src/commands/ci-setup.js +0 -65
- package/src/commands/implement.js +0 -664
- package/src/commands/init.js +0 -770
- package/src/commands/list-projects.js +0 -77
- package/src/commands/memory.js +0 -171
- package/src/commands/run.js +0 -919
- package/src/commands/setup-scripts.js +0 -114
- package/src/commands/upload.js +0 -162
- package/src/commands/video.js +0 -30
- package/src/commands/workflow.js +0 -368
- package/src/config/config.js +0 -117
- package/src/config/environments.js +0 -145
- package/src/utils/execution-context.js +0 -25
- package/src/utils/progress-reporter.js +0 -155
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import{runTest as Ie,logger as $,DEFAULT_OUTPUT_BASE as me,SESSIONS_DIR as ve}from"@zibby/core";import{zibbyScratchSpecsDir as W}from"@zibby/core/constants/zibby-scratch.js";import{createCliRunIndexPipelineProgressAppender as _e,postCliInterruptedRunIndex as te}from"@zibby/core/utils/run-index-post-cli.js";import{composePipelineProgressWithStudioCliLog as ne}from"../utils/studio-cli-log-mirror.js";import{registerChatOrchestratedRun as xe,unregisterChatOrchestratedRun as $e}from"../utils/chat-run-lifecycle.js";import{readFileSync as _,existsSync as p,statSync as se,mkdirSync as re,writeFileSync as le,appendFileSync as Pe,rmSync as ce}from"fs";import{resolve as T,join as A,dirname as Ee,isAbsolute as ie}from"path";import{fileURLToPath as Ae}from"url";import{glob as q}from"glob";import e from"chalk";import ae from"ora";import Se from"dotenv";import be from"open";import{getApiUrl as J,getAccountApiUrl as Te,getCurrentEnvironment as de,getFrontendUrl as ue}from"../config/environments.js";import{getSessionToken as ge,getUserInfo as pe}from"../config/config.js";const Ce=Ae(import.meta.url),ke=Ee(Ce),Ue=JSON.parse(_(A(ke,"../../package.json"),"utf-8"));function Oe(n){const o=l=>l?typeof l.message=="string"&&l.message.includes("Interrupted by user")?!0:Array.isArray(l.errors)?l.errors.some(o):l.cause?o(l.cause):!1:!1;return o(n)}function fe(n,o){if(n==null)return null;const l=String(n).trim();return l?ie(l)?l:T(o,l):null}function Be(n,o){try{if(!n||!p(n))return;const l=A(n,"studio-cli.log"),w=`
|
|
2
|
+
[CLI_FATAL] ${new Date().toISOString()}
|
|
3
|
+
Error: ${String(o?.message||o)}
|
|
4
|
+
${o?.stack?`${String(o.stack)}
|
|
5
|
+
`:""}`;Pe(l,w,"utf8")}catch{}}process.env.DOTENV_CONFIG_QUIET="true";const je=process.env.NODE_ENV||"development",Re=[T(process.cwd(),".env.local"),T(process.cwd(),`.env.${je}`),T(process.cwd(),".env")];Re.forEach(n=>{p(n)&&Se.config({path:n,override:!1})});function Ye(n,o){const l=o?.paths?.specs||"test-specs",w=o?.paths?.generated||"tests";return n.replace(l,w).replace(/\.txt$/,".spec.js")}async function Ne(n,o){const l=ue(),w=/^[0-9A-HJKMNP-TV-Z]{26}$/,S=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;if(!w.test(n)&&!S.test(n))return console.log(e.red(`
|
|
6
|
+
\u274C Invalid execution ID format (security check)
|
|
7
|
+
`)),!1;if(!S.test(o))return console.log(e.red(`
|
|
8
|
+
\u274C Invalid project ID format (security check)
|
|
9
|
+
`)),!1;const I=new URL(l);if(!l.includes("localhost")&&!l.includes("127.0.0.1")&&I.protocol!=="https:")return console.log(e.red(`
|
|
10
|
+
\u274C Frontend URL must use HTTPS in production
|
|
11
|
+
`)),!1;const k=`${l}/projects/${o}/runs/${n}`;try{return console.log(e.cyan(`
|
|
12
|
+
Opening test results in browser...`)),console.log(e.gray(` ${k}
|
|
13
|
+
`)),await be(k),!0}catch{return console.log(e.yellow("Could not open browser automatically")),console.log(e.gray(` Please open manually: ${k}
|
|
14
|
+
`)),!1}}function X(n,o){const l=o?.paths?.specs||"test-specs",w=o?.paths?.generated||"tests";return n.replace(w,l).replace(/\.spec\.js$/,".txt")}async function Ge({apiKey:n,specPath:o,fullSpecPath:l,collectionIdOrName:w,folder:S,agent:I,config:r,result:k,shouldOpen:B}){const R=J(),N=de(),F=process.env.ZIBBY_USER_TOKEN||ge(),U=pe();console.log(U?e.gray(`Authenticated: ${U.email} (${U.name||"User"})`):e.gray("Authenticated: Using Personal Access Token"));const f=ae(`Uploading to ${N.name}...`).start();try{const a=process.cwd();let x=null;if(k?.state?.sessionPath){const s=k.state.sessionPath;x=s.startsWith(a)?s.slice(a.length+1):s}if(!x){const d=`${r.paths?.output||me}/${ve}/*`,b=q.sync(d,{cwd:a}).filter(E=>se(A(a,E)).isDirectory()&&!/session_/.test(E)).map(E=>({path:E,stat:se(A(a,E))})).sort((E,oe)=>oe.stat.mtimeMs-E.stat.mtimeMs);if(b.length===0){f.warn("No session folder found, skipping upload");return}x=b[0].path}let P=q.sync(`${x}/execute_live/videos/*.webm`,{cwd:a});P.length===0&&(P=q.sync(`${x}/execute_live/*.webm`,{cwd:a})),P.length===0&&(P=q.sync(`${x}/*.webm`,{cwd:a}));const O=A(a,x,"execute_live","events.json"),G=A(a,x,"execute_live","result.json"),Y=A(a,x,"title.txt"),M=Ye(o,r),t=A(a,M),c=p(t);if(P.length===0){f.warn("No video file found in session folder, skipping upload");return}const u=A(a,P[0]),m=p(Y)?_(Y,"utf-8").trim():null;$.debug(`Video exists: ${p(u)}`),$.debug(`Events exists: ${p(O)}`);let h=null,j=null;if(w){f.text="Resolving collection...";const s=Te(),d=await fetch(`${s}/collections/resolve`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({collectionIdOrName:w})});if(!d.ok){const E=await d.json().catch(()=>({}));throw new Error(E.error||"Failed to resolve collection")}const b=await d.json();h=b.collectionId,j=b.collectionName,b.isNew&&(f.text=`Created new collection: ${j}`)}f.text="Preparing upload...";const g={video:p(u)?_(u).length:0,events:p(O)?_(O).length:0,instructions:(()=>{const s=X(o,r),d=A(a,s);return p(d)?_(d).length:0})(),result:p(G)?_(G).length:0},v=g.video+g.events+g.instructions+g.result,Z=(v/1024/1024).toFixed(2);if(g.video>50*1024*1024)throw new Error(`Video file too large: ${(g.video/1024/1024).toFixed(2)}MB (max: 50MB)`);if(v>60*1024*1024)throw new Error(`Total upload size too large: ${Z}MB (max: 60MB)`);f.text=`Getting upload URLs... (${Z}MB)`;const i={"Content-Type":"application/json",Authorization:`Bearer ${n}`};F&&(i["X-User-Token"]=F);const y=typeof I=="object"?I.provider:I||"cursor",z=typeof I=="object"&&I[y]?.model||null,he=z==="auto"?null:z,L=await fetch(`${R}/executions/upload`,{method:"POST",headers:i,body:JSON.stringify({collectionId:h||null,specPath:o,specContent:(()=>{const s=X(o,r),d=A(a,s);return p(d)?_(d,"utf-8"):p(l)?_(l,"utf-8"):""})(),title:m||null,agentType:y,model:he,metadata:{agent:I||"cursor",timestamp:new Date().toISOString(),folder:S||null,collectionName:j||null},fileSizes:g})});if(!L.ok){const s=await L.json();if(L.status===413)throw s.details&&Array.isArray(s.details)?new Error(`File size limit exceeded:
|
|
15
|
+
${s.details.map(d=>` \u2022 ${d}`).join(`
|
|
16
|
+
`)}
|
|
17
|
+
|
|
18
|
+
Compress your files or reduce recording quality.`):new Error(s.error||"Upload files too large");if(L.status===429){if(s.quotaInfo)throw new Error(`Video verification quota exceeded (${s.quotaInfo.used}/${s.quotaInfo.limit}).
|
|
19
|
+
Plan: ${s.quotaInfo.planId}. Resets: ${new Date(s.quotaInfo.periodEnd).toLocaleDateString()}
|
|
20
|
+
Upgrade at: https://studio.zibby.app/billing`);if(s.storageInfo){const d=(s.storageInfo.used/1073741824).toFixed(2),b=(s.storageInfo.limit/(1024*1024*1024)).toFixed(2),E=(s.storageInfo.uploadSize/(1024*1024)).toFixed(2);throw new Error(`Storage quota would be exceeded.
|
|
21
|
+
Current usage: ${d}GB / ${b}GB
|
|
22
|
+
This upload: ${E}MB
|
|
23
|
+
Plan: ${s.storageInfo.planId}
|
|
24
|
+
Upgrade at: https://studio.zibby.app/billing`)}}throw new Error(s.error||"Failed to initiate upload")}const{executionId:K,uploadUrls:C,isOrphan:ye,projectId:H}=await L.json();f.text=`Uploading video... (${K})`;const V=_(u);$.debug(`Video buffer size: ${V.length} bytes`),$.debug("Upload URL received (redacted)");const D=await fetch(C.video,{method:"PUT",body:V,headers:{"Content-Type":"video/webm","Content-Length":V.length.toString()}});if($.debug(`Video upload status: ${D.status} ${D.statusText}`),!D.ok){const s=await D.text();throw new Error(`Failed to upload video: ${D.status} ${D.statusText} - ${s}`)}if($.info("Video uploaded successfully"),p(O)&&C.events){f.text="Uploading events...";const s=_(O),d=await fetch(C.events,{method:"PUT",body:s,headers:{"Content-Type":"application/json","Content-Length":s.length.toString()}});if($.debug(`Events upload status: ${d.status} ${d.statusText}`),!d.ok)throw new Error("Failed to upload events");$.info("Events uploaded successfully")}else $.debug(`Skipping events upload - exists: ${p(O)}, hasUrl: ${!!C.events}`);if(c&&C.generatedTest){f.text="Uploading generated test...";const s=_(t);if(!(await fetch(C.generatedTest,{method:"PUT",body:s,headers:{"Content-Type":"application/javascript","Content-Length":s.length.toString()}})).ok)throw new Error("Failed to upload generated test")}if(C.testInstructions){const s=X(o,r),d=A(a,s);if(p(d)){f.text="Uploading test instructions...";const b=_(d);(await fetch(C.testInstructions,{method:"PUT",body:b,headers:{"Content-Type":"text/plain","Content-Length":b.length.toString()}})).ok?$.debug("Test instructions uploaded successfully"):$.warn("Failed to upload test instructions (non-blocking)")}else console.log(`\u2139\uFE0F No source test instructions found at ${s}`)}if(C.result&&p(G)){f.text="Uploading test results...";const s=_(G);(await fetch(C.result,{method:"PUT",body:s,headers:{"Content-Type":"application/json","Content-Length":s.length.toString()}})).ok?$.debug("result.json uploaded successfully"):$.warn("Failed to upload result.json (non-blocking)")}if(f.text="Confirming upload...",!(await fetch(`${R}/executions/${K}/confirm`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify({status:"completed",metadata:{uploadedAt:new Date().toISOString()},fileSizes:g})})).ok)throw new Error("Failed to confirm upload");f.succeed("Uploaded to Zibby Cloud!"),console.log(e.gray(`
|
|
25
|
+
${"\u2501".repeat(50)}`)),console.log(e.white(`Execution ID: ${e.cyan(K)}`));const ee=ue();console.log(ye?e.white(`Location: ${e.yellow("History tab")}`):e.white(`Collection: ${e.cyan(h)}`));const we=H?`${ee}/projects/${H}/runs/${K}`:`${ee}/history?execution=${K}`;return console.log(e.white(`View execution: ${e.cyan(we)}`)),console.log(e.gray(`${"\u2501".repeat(50)}
|
|
26
|
+
`)),{success:!0,executionId:K,projectId:H||null}}catch(a){return f.fail("Upload failed"),console.log(e.red(` ${a.message}`)),process.env.DEBUG&&console.log(e.gray(` Stack: ${a.stack}`)),console.log(e.gray(` Continuing without upload...
|
|
27
|
+
`)),{success:!1}}}async function Ke(n,o,l){const w=J(),S=o.join(","),I=await fetch(`${w}/executions/${n}/test-cases?ids=${S}`,{headers:{Authorization:`Bearer ${l}`}});if(!I.ok)throw new Error(`Failed to fetch test cases: ${I.status} ${I.statusText}`);return(await I.json()).testCases||[]}async function De(n,o){const l=de();if(console.log(e.bold.cyan(`
|
|
28
|
+
Zibby Test Automation
|
|
29
|
+
`)),console.log(e.gray(`@zibby/cli v${Ue.version} | Node.js ${process.version}`)),console.log(e.gray("\u2501".repeat(50))),o.sources){o.execution||(console.log(e.red(`
|
|
30
|
+
\u274C --execution <id> is required when using --sources
|
|
31
|
+
`)),process.exit(1));const t=o.project||process.env.ZIBBY_API_KEY;t||(console.log(e.red(`
|
|
32
|
+
\u274C --project <id> or ZIBBY_API_KEY required when using --sources
|
|
33
|
+
`)),process.exit(1));const c=o.sources.split(",").map(h=>h.trim());console.log(e.white(`Fetching ${c.length} test case(s) from execution ${e.cyan(o.execution)}...`));const u=await Ke(o.execution,c,t);u.length===0&&(console.log(e.red(`
|
|
34
|
+
No test cases found for the given IDs
|
|
35
|
+
`)),process.exit(1)),console.log(e.green(`\u2713 Fetched ${u.length} test case(s)`));const m=W(process.cwd());re(m,{recursive:!0});for(const h of u){const j=`# ${h.title}
|
|
36
|
+
|
|
37
|
+
${h.content}`,g=T(m,`${h.id}.txt`);le(g,j,"utf-8"),console.log(e.gray("\u2501".repeat(50))),console.log(e.white(`
|
|
38
|
+
Running: ${e.cyan(h.title)} (${h.id})`));const v={...o,sources:void 0,execution:void 0};v.sync=!0,await De(g,v)}ce(m,{recursive:!0,force:!0});return}!n&&!o.sources&&(console.log(e.red(`
|
|
39
|
+
\u274C Missing test spec path
|
|
40
|
+
`)),console.log(e.white("Usage:")),console.log(e.gray(" zibby test test-specs/login.txt Run a spec file")),console.log(e.gray(` zibby test "login and check dashboard" Run inline spec
|
|
41
|
+
`)),process.exit(1));const w=n&&!n.endsWith(".txt")&&!p(T(process.cwd(),n))&&n.includes(" ");if(w){const t=W(process.cwd());re(t,{recursive:!0});const c=T(t,`inline-${Date.now()}.txt`);le(c,n,"utf-8"),n=c}const S=T(process.cwd(),n);p(S)||(console.log(e.red(`
|
|
42
|
+
\u274C Test spec not found: ${n}
|
|
43
|
+
`)),process.exit(1));const I=_(S,"utf-8");let r={headless:o.headless||!1,playwrightArtifacts:!0};const k=T(process.cwd(),o.config);if(p(k))try{const t=await import(k),c=u=>u?.provider?u.provider:u?.gemini?"gemini":u?.codex?"codex":u?.claude?"claude":u?.cursor?"cursor":null;r={...r,...t.default,agent:t.default?.agent?{...t.default.agent,provider:o.agent||c(t.default.agent)}:{provider:o.agent||"cursor"},mcp:t.default?.browser?.mcp||"playwright",headless:o.headless!==void 0?o.headless:t.default?.browser?.headless,paths:t.default?.paths||{specs:"test-specs",generated:"tests"},cloudSync:t.default?.cloudSync||!1}}catch{console.log(e.yellow(`\u26A0\uFE0F Could not load config from ${o.config}`)),r.agent={provider:o.agent||"cursor"},r.cloudSync=!1}else r.agent={provider:o.agent||"cursor"},r.cloudSync=!1;let B=process.env.ZIBBY_API_KEY;const R=o.sync||r.cloudSync===!0&&o.sync!==!1;if(o.project&&o.project.startsWith("zby_")&&(B=o.project,console.log(e.gray("Using --project as API token (overriding .env)"))),R&&!B&&(console.log(e.red(`
|
|
44
|
+
\u274C Error: Cloud sync is enabled but ZIBBY_API_KEY not set
|
|
45
|
+
`)),console.log(e.white("Cloud sync is enabled in .zibby.config.js")),console.log(e.white(`You need to set ZIBBY_API_KEY in .env file
|
|
46
|
+
`)),console.log(e.white("Option 1: Add to .env file:")),console.log(e.gray(` ZIBBY_API_KEY=zby_your_key_here
|
|
47
|
+
`)),console.log(e.white("Option 2: Disable cloud sync:")),console.log(e.gray(` Edit .zibby.config.js \u2192 cloudSync: false
|
|
48
|
+
`)),console.log(e.white(`Get your API key from: https://zibby.dev/dashboard
|
|
49
|
+
`)),process.exit(1)),R&&B){let t=process.env.ZIBBY_USER_TOKEN,c;t?console.log(e.gray("Using Personal Access Token from ZIBBY_USER_TOKEN")):(t=ge(),c=pe(),(!t||!c)&&(console.log(e.red(`
|
|
50
|
+
\u274C Error: User authentication required for uploads
|
|
51
|
+
`)),console.log(e.gray(`Choose one of these options:
|
|
52
|
+
`)),console.log(e.cyan(" Option 1 (Local Development):")),console.log(e.gray(` zibby login
|
|
53
|
+
`)),console.log(e.cyan(" Option 2 (CI/CD):")),console.log(e.gray(" 1. Generate a Personal Access Token at:")),console.log(e.gray(" https://zibby.app/settings/tokens")),console.log(e.gray(" 2. Set environment variable:")),console.log(e.gray(` export ZIBBY_USER_TOKEN=zby_pat_xxxxx
|
|
54
|
+
`)),process.exit(1)),c&&console.log(e.gray(`Authenticated: ${c.email} (${c.name||"User"})`)))}if(R&&B){const t=J();try{B.startsWith("zby_")||(console.log(e.red(`
|
|
55
|
+
\u274C Invalid API token format
|
|
56
|
+
`)),console.log(e.white('This CLI requires a project API token (starts with "zby_").')),console.log(e.gray(` Format: zby_***
|
|
57
|
+
`)),console.log(e.white(`Get your project API token from settings:
|
|
58
|
+
`)),console.log(e.gray(" Local: http://localhost:3001/settings")),console.log(e.gray(` Prod: https://app.zibby.app/settings
|
|
59
|
+
`)),process.exit(1));const c=await fetch(`${t}/projects`,{headers:{Authorization:`Bearer ${B}`}});c.ok||(console.log(e.red(`
|
|
60
|
+
\u274C Unauthorized: Invalid project API token
|
|
61
|
+
`)),console.log(e.white("Your project API token is invalid or does not have access.")),console.log(e.gray(` Token format: zby_***
|
|
62
|
+
`)),console.log(e.white(`Get your project API token from settings:
|
|
63
|
+
`)),console.log(e.gray(" Local: http://localhost:3001/settings")),console.log(e.gray(` Prod: https://app.zibby.app/settings
|
|
64
|
+
`)),process.exit(1));const m=(await c.json()).projects||[];(!m||m.length===0)&&(console.log(e.red(`
|
|
65
|
+
\u274C No projects found for this token
|
|
66
|
+
`)),process.exit(1));const h=m[0];console.log(e.green(`\u2713 Authorized for project: ${h.name} (${h.projectId})`))}catch(c){console.log(e.red(`
|
|
67
|
+
\u274C Failed to validate API credentials: ${c.message}
|
|
68
|
+
`)),process.exit(1)}}const N=typeof r.agent=="object"?r.agent.provider:r.agent;if(N==="claude"&&!process.env.ANTHROPIC_API_KEY&&(console.log(e.red(`
|
|
69
|
+
\u274C Error: ANTHROPIC_API_KEY not set
|
|
70
|
+
`)),console.log(e.white("Set your API key:")),console.log(e.gray(` export ANTHROPIC_API_KEY="sk-ant-..."
|
|
71
|
+
`)),console.log(e.white("Or add to .env file:")),console.log(e.gray(` ANTHROPIC_API_KEY=sk-ant-...
|
|
72
|
+
`)),console.log(e.white(`Get your API key from: https://console.anthropic.com/
|
|
73
|
+
`)),process.exit(1)),N==="codex"){try{const{execSync:t}=await import("child_process");t("codex --version",{encoding:"utf-8",timeout:5e3,stdio:"pipe"})}catch{console.log(e.red(`
|
|
74
|
+
\u274C Error: Codex CLI not installed
|
|
75
|
+
`)),console.log(e.white("Install the Codex CLI:")),console.log(e.gray(` npm install -g @openai/codex
|
|
76
|
+
`)),console.log(e.white("Then verify:")),console.log(e.gray(` codex --version
|
|
77
|
+
`)),process.exit(1)}!process.env.OPENAI_API_KEY&&!process.env.CODEX_API_KEY&&(console.log(e.red(`
|
|
78
|
+
\u274C Error: OPENAI_API_KEY not set
|
|
79
|
+
`)),console.log(e.white("Set your API key:")),console.log(e.gray(` export OPENAI_API_KEY="sk-..."
|
|
80
|
+
`)),console.log(e.white("Or add to .env file:")),console.log(e.gray(` OPENAI_API_KEY=sk-...
|
|
81
|
+
`)),console.log(e.white(`Get your API key from: https://platform.openai.com/api-keys
|
|
82
|
+
`)),process.exit(1))}if(N==="gemini"){if(process.env.GEMINI_API_KEY&&process.env.GOOGLE_API_KEY){const t=String(process.env.GEMINI_API_KEY).trim(),c=String(process.env.GOOGLE_API_KEY).trim();t&&c&&t!==c&&(process.env.GOOGLE_API_KEY=t,console.log(e.yellow("\u26A0\uFE0F Both GOOGLE_API_KEY and GEMINI_API_KEY are set; forcing GOOGLE_API_KEY to GEMINI_API_KEY for this run.")))}try{const{execSync:t}=await import("child_process");t("gemini --version",{encoding:"utf-8",timeout:5e3,stdio:"pipe"})}catch{console.log(e.red(`
|
|
83
|
+
\u274C Error: Gemini CLI not installed
|
|
84
|
+
`)),console.log(e.white("Install the Gemini CLI:")),console.log(e.gray(` npm install -g @google/gemini-cli
|
|
85
|
+
`)),console.log(e.white("Then verify:")),console.log(e.gray(` gemini --version
|
|
86
|
+
`)),process.exit(1)}!process.env.GEMINI_API_KEY&&!process.env.GOOGLE_API_KEY&&(console.log(e.red(`
|
|
87
|
+
\u274C Error: GEMINI_API_KEY not set
|
|
88
|
+
`)),console.log(e.white("Set your API key:")),console.log(e.gray(` export GEMINI_API_KEY="your_key_here"
|
|
89
|
+
`)),console.log(e.white("Or add to .env file:")),console.log(e.gray(` GEMINI_API_KEY=your_key_here
|
|
90
|
+
`)),console.log(e.white(`Get your API key from: https://aistudio.google.com/app/apikey
|
|
91
|
+
`)),process.exit(1))}if(N==="cursor"){const{checkCursorAgentInstalled:t,getCursorAgentInstallInstructions:c}=await import("@zibby/core");await t()||(console.log(e.red(c())),process.exit(1));const m=process.env.CI||process.env.GITLAB_CI||process.env.CIRCLECI||process.env.GITHUB_ACTIONS;process.env.CURSOR_API_KEY||(m?(console.log(e.red(`
|
|
92
|
+
\u274C Error: CURSOR_API_KEY not set
|
|
93
|
+
`)),console.log(e.white("In CI/CD, you must provide your Cursor API key:")),console.log(e.gray(` export CURSOR_API_KEY="your-cursor-token-here"
|
|
94
|
+
`)),console.log(e.white(`Get your API key from: https://cursor.com/settings
|
|
95
|
+
`)),console.log(e.white(`Add it to your CI/CD environment variables.
|
|
96
|
+
`)),process.exit(1)):console.log(e.yellow(`\u26A0\uFE0F CURSOR_API_KEY not set (using locally stored Cursor credentials)
|
|
97
|
+
`)))}console.log(e.white(`Test Spec: ${e.cyan(n)}`));const F=typeof r.agent=="object"?r.agent.provider:r.agent;console.log(e.white(`Agent: ${e.cyan(F)}`)),console.log(e.white(`Browser: ${e.cyan(r.mcp||"playwright")} ${r.headless?e.gray("(headless)"):e.gray("(headed)")}`)),r.cloudSync&&console.log(e.white(`Cloud Sync: ${e.green("enabled")}`)),o.autoApprove&&console.log(e.white(`Auto-approve: ${e.yellow("enabled (CI/CD mode)")}`)),o.collection?console.log(e.white(`Collection: ${e.cyan(o.collection)}${o.folder?` / ${o.folder}`:""}`)):console.log(e.gray("Mode: Orphan execution (History tab)"));let U;if(o.sessionPath!=null&&String(o.sessionPath).trim()!==""){const t=String(o.sessionPath).trim();U=ie(t)?T(t):T(process.cwd(),t),console.log(e.white(`Session path: ${e.cyan(U)}`))}console.log(e.gray("\u2501".repeat(50)));const f=ae({text:"Initializing...",stream:process.stdout}).start(),a=()=>{try{te({cwd:process.cwd(),config:r})}catch{}},x=process.env.ZIBBY_CHAT_OWNER_PID,P=x!=null&&String(x).trim()!==""?Number(x):null,O=P!=null&&Number.isFinite(P)&&P>0,G=()=>{if(O)try{$e(process.cwd(),P,process.pid,r)}catch{}};let Y=null,M=fe(U,process.cwd());try{f.stop();const{waitUntilRunCapacity:t}=await import("@zibby/core/utils/run-capacity-coordinator.js"),{basename:c}=await import("path");await t({cwd:process.cwd(),config:r,meta:{source:process.env.ZIBBY_RUN_SOURCE||"cli",specHint:n?c(n):"",studioTestCaseId:process.env.ZIBBY_STUDIO_TEST_CASE_ID||null},log:i=>console.log(e.gray(i))});const u=await import("@zibby/core/templates/browser-test-automation/graph.mjs").catch(()=>null);if(O)try{xe(process.cwd(),P,process.pid,r)}catch{}process.on("SIGINT",a),process.on("SIGTERM",a);const m=process.cwd(),h={},j=i=>{const y=fe(i?.sessionPath,m);y&&(M=y)};if(typeof r.onPipelineProgress=="function"){const i=ne(r.onPipelineProgress,m,r);Y=()=>i.dispose(),h.onPipelineProgress=y=>{j(y),i({...y,specPath:n,taskDescription:I})}}else if(r.runIndex?.pipelineProgress!==!1){const i=_e({cwd:m,config:r}),y=ne(i,m,r);Y=()=>y.dispose(),h.onPipelineProgress=z=>{j(z),y({...z,specPath:n,taskDescription:I})}}const g=await Ie(S,{...r,...h,specPath:n,apiKey:process.env.ANTHROPIC_API_KEY,autoApprove:o.autoApprove,force:o.autoApprove,singleNode:o.node,sessionId:o.session,...U?{sessionPath:U}:{},workflow:o.workflow,contextConfig:r.context,fallbackAgentModule:u});let v=g.outputPath||null;v&&(v=v.replace(/\/+/g,"/"),v.includes(process.cwd())&&(v=v.replace(`${process.cwd()}/`,"")));const Z=g.state?.generate_script?.scriptPath||g.state?.verify_script?.success;if(console.log(e.gray(`
|
|
98
|
+
${"\u2501".repeat(50)}`)),Z&&v)console.log(e.green("\u2713 Test execution completed")),console.log(e.white(`Generated test: ${e.cyan(v)}`)),g.toolCount!==void 0&&console.log(e.white(`Tools used: ${e.cyan(g.toolCount)}`)),console.log(e.gray(`${"\u2501".repeat(50)}
|
|
99
|
+
`)),console.log(e.cyan("Next steps:")),console.log(e.white(` 1. Review generated test: ${v}`)),console.log(e.white(` 2. Run with Playwright: npx playwright test ${v}`)),console.log(e.white(` 3. View report: npx playwright show-report
|
|
100
|
+
`));else{const i=g.state?.execute_live;i?i.steps?.length>0||i.actions?.length>0?(console.log(e.white(`
|
|
101
|
+
Execution Summary:`)),console.log(e.white(` Steps: ${i.steps?.length||0}`)),console.log(e.white(` Actions: ${i.actions?.length||0}`)),i.finalUrl&&console.log(e.white(` Final URL: ${i.finalUrl}`)),i.notes&&console.log(e.white(`
|
|
102
|
+
\u{1F4DD} ${i.notes}`))):(console.log(e.red("\u2716 Workflow did not complete")),console.log(e.yellow(`
|
|
103
|
+
\u26A0\uFE0F ${i.notes||"Test execution failed"}`))):console.log(e.red("\u2716 Workflow did not complete")),console.log(e.gray(`${"\u2501".repeat(50)}
|
|
104
|
+
`))}if(o.node)console.log(e.gray(`
|
|
105
|
+
Note: Upload skipped for single node execution`)),console.log(e.gray(` Run full workflow to upload all artifacts
|
|
106
|
+
`));else if(R){const i=o.project||process.env.ZIBBY_API_KEY;if(!i)console.log(e.yellow(`
|
|
107
|
+
Cloud sync enabled but no API key provided`)),console.log(e.gray(" Option 1: Use --project flag: --project zby_xxx")),console.log(e.gray(` Option 2: Add to .env: ZIBBY_API_KEY=zby_xxx
|
|
108
|
+
`));else{const y=await Ge({apiKey:i,specPath:n,fullSpecPath:S,projectId:null,collectionIdOrName:o.collection||null,folder:o.folder,agent:r.agent,config:r,result:g,shouldOpen:o.open});o.open&&y?.success&&y.executionId&&y.projectId&&await Ne(y.executionId,y.projectId)}}}catch(t){if(f.isSpinning&&f.stop(),console.log(e.red(`
|
|
109
|
+
\u2716 Test failed
|
|
110
|
+
`)),Oe(t)){console.log(e.yellow(`Test execution was interrupted
|
|
111
|
+
`));try{te({cwd:process.cwd(),config:r})}catch{}Q(w),process.exit(130)}Be(M,t),console.log(e.red(`Error: ${t.message}
|
|
112
|
+
`)),t.stack&&console.log(e.gray(t.stack)),Q(w),process.exit(1)}finally{typeof Y=="function"&&Y(),G(),process.off("SIGINT",a),process.off("SIGTERM",a)}Q(w)}function Q(n){if(n)try{ce(W(process.cwd()),{recursive:!0,force:!0})}catch{}}export{De as runCommand};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import{spawn as d}from"child_process";import{join as u,dirname as y}from"path";import{createRequire as P}from"module";import o from"chalk";const C=P(import.meta.url);function f(){try{return y(C.resolve("@zibby/core/package.json"))}catch{return null}}async function g(e,i,t=!1,l=!1,c="on",n=null){const r=f();if(!r)throw new Error("@zibby/core not found. Run: npm install @zibby/core");const m=u(r,"scripts",e),s={...process.env};return t||(s.ZIBBY_HEADLESS="1"),l&&(s.ZIBBY_CLOUD_SYNC="1"),c&&(s.ZIBBY_VIDEO=c),n&&(s.ZIBBY_VIEWPORT_WIDTH=String(n.width||1280),s.ZIBBY_VIEWPORT_HEIGHT=String(n.height||720)),new Promise((w,p)=>{const h=d("bash",[m],{cwd:process.cwd(),stdio:"inherit",env:s});h.on("close",a=>{a===0?w({success:!0}):p(new Error(`Script exited with code ${a}`))}),h.on("error",a=>{p(a)})})}async function S(e={}){await g("setup-official-playwright-mcp.sh","Setting up official Playwright MCP",e.headed,e.cloudSync,e.video,e.viewport)}async function _(e){try{await g("setup-ci.sh","Complete CI/CD setup"),console.log(o.green(`
|
|
2
|
+
\u2705 CI/CD setup complete!
|
|
3
|
+
`))}catch(i){console.log(o.red(`
|
|
4
|
+
\u274C Error: ${i.message}
|
|
5
|
+
`)),process.exit(1)}}async function x(e,i){try{const t=f();if(!t)throw new Error("@zibby/core not found. Run: npm install @zibby/core");const c=[u(t,"scripts","test-with-video.sh"),e||"tests/"];i.headed&&c.push("headed"),console.log(o.cyan(`
|
|
6
|
+
\u{1F3A5} Running tests with video recording...
|
|
7
|
+
`)),console.log(o.gray("\u2501".repeat(50)));const n=d("bash",c,{cwd:process.cwd(),stdio:"inherit"});n.on("close",r=>{r===0?console.log(o.green(`
|
|
8
|
+
\u2705 Tests complete!
|
|
9
|
+
`)):(console.log(o.red(`
|
|
10
|
+
\u274C Tests failed with code ${r}
|
|
11
|
+
`)),process.exit(r))}),n.on("error",r=>{console.log(o.red(`
|
|
12
|
+
\u274C Error: ${r.message}
|
|
13
|
+
`)),process.exit(1)})}catch(t){console.log(o.red(`
|
|
14
|
+
\u274C Error: ${t.message}
|
|
15
|
+
`)),process.exit(1)}}export{_ as setupCiCommand,S as setupPlaywrightMcpCommand,x as testWithVideoCommand};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import ae from"express";import{createServer as Ve}from"http";import{WebSocketServer as Ze}from"ws";import{spawn as Q,execSync as X}from"child_process";import{readFileSync as k,existsSync as l,readdirSync as L,statSync as F,createReadStream as qe,createWriteStream as Qe,mkdirSync as Te,openSync as Xe,readSync as et,closeSync as tt,writeFileSync as ee,unlinkSync as st}from"fs";import{join as a,resolve as x,dirname as Ne,basename as ot}from"path";import{homedir as nt}from"os";import{inspect as rt}from"util";import{fileURLToPath as it,pathToFileURL as ct}from"url";import _e from"open";import at from"dotenv";import{installStudio as lt,isStudioInstalled as ut}from"../utils/studio-installer.js";import{launchStudio as dt}from"../utils/studio-launcher.js";import{mergeSessionRunState as B,listRunningSessionStatesFromSessionsRoot as ft}from"@zibby/core/utils/run-state-session.js";import{liveRunsFromSessionStateRows as pt}from"@zibby/core/utils/session-state-live-runs.js";import{findLatestLiveFrameFileSync as Ae,readLatestLiveFramePayloadSync as ke}from"@zibby/core/utils/live-frame-discovery.js";import{getApiUrl as Ee}from"../config/environments.js";const mt=it(import.meta.url),le=Ne(mt);function Oe(j){const g=a(j,"video.webm");if(l(g))return g;const I=a(j,"execute_live");if(!l(I))return null;try{const E=L(I).filter(S=>S.endsWith(".webm"));return E.length===0?null:E.map(S=>{const T=a(I,S);try{return{p:T,mtime:F(T).mtimeMs}}catch{return{p:T,mtime:0}}}).sort((S,T)=>T.mtime-S.mtime)[0].p}catch{return null}}function xe(j){if(process.platform==="win32")try{const g=X("netstat -ano",{encoding:"utf8"}),I=new Set;for(const E of g.split(`
|
|
3
|
+
`)){if(!E.includes("LISTENING"))continue;const w=E.trim().split(/\s+/);if(!(w[1]||"").endsWith(`:${j}`))continue;const T=w[w.length-1];/^\d+$/.test(T)&&I.add(parseInt(T,10))}return[...I].filter(E=>E!==process.pid)}catch{return[]}try{const g=X(`lsof -tiTCP:${j} -sTCP:LISTEN`,{encoding:"utf8"}).trim();return[...new Set(g.split(`
|
|
4
|
+
`).filter(Boolean))].map(I=>parseInt(I,10)).filter(I=>!Number.isNaN(I)&&I!==process.pid)}catch{return[]}}async function Ce(j){let g=xe(j);if(g.length!==0){console.log(`[Studio] Port ${j} in use \u2014 stopping previous Studio listener(s): ${g.join(", ")}`);for(const I of g)try{process.kill(I,"SIGTERM")}catch{}await new Promise(I=>setTimeout(I,450)),g=xe(j);for(const I of g)try{process.kill(I,"SIGKILL")}catch{}await new Promise(I=>setTimeout(I,200))}}async function Rt(j={}){const g=ae(),I=Ve(g),E=new Ze({server:I}),w=j.port||3847,S=process.cwd(),T=nt(),G=5173;process.env.DOTENV_CONFIG_QUIET="true";const Re=process.env.NODE_ENV||"development";[x(S,".env.local"),x(S,`.env.${Re}`),x(S,".env")].forEach(s=>{l(s)&&at.config({path:s,override:!1})});const C=new Map,U=new Map;let W=null,$e=null;const J=96e3,ue="studio-cli.log",M="studio-run.json",Fe=".zibby-studio-stop",te=512*1024;g.use(ae.json()),g.get("/api/session-run-states",(s,e)=>{try{let t=O();try{const r=new URL(s.url,"http://zibby.studio").searchParams.get("sessionsRoot");if(r&&String(r).trim()){const c=x(decodeURIComponent(String(r).trim()));l(c)&&F(c).isDirectory()&&(t=c)}}catch{}const n=ft(t),{liveIdList:o,progressByKey:i}=pt(n);e.json({rows:n,liveIdList:o,progressByKey:i,sessionsRoot:t,unavailable:!1})}catch(t){e.status(500).json({error:t.message,rows:[],liveIdList:[],progressByKey:{},unavailable:!0})}}),g.use((s,e,t)=>(e.header("Access-Control-Allow-Origin","*"),e.header("Access-Control-Allow-Methods","GET, POST, PUT, DELETE"),e.header("Access-Control-Allow-Headers","Content-Type"),t()));const z=a(le,"../../../../studio"),se=l(a(z,"package.json"));console.log("[Studio] Checking for source at:",z),console.log("[Studio] Is monorepo:",se);let K=!1;function Le(){const s=a(z,"node_modules",".bin",process.platform==="win32"?"electron.cmd":"electron"),e=l(s),o=Q(e?s:"npx",e?["."]:["electron","."],{cwd:z,detached:!0,stdio:"ignore",shell:!1,env:{...process.env,ZIBBY_STUDIO_PROJECT_ROOT:S,ZIBBY_STUDIO_API_BASE:`http://localhost:${w}/api`}});o.unref(),$e=o}async function de(){if(se){Le();return}ut()||await lt(),dt({port:w,sessionsDir:O()})}if(se){console.log("\u{1F4E6} Detected monorepo - using development mode");try{K=(await fetch(`http://localhost:${G}`)).ok,console.log("\u2705 Found existing Vite dev server")}catch{console.log("\u{1F680} Starting Vite dev server..."),W=Q("npm",["run","dev"],{cwd:z,stdio:"inherit",shell:!0}),await new Promise(e=>{let t=0;const n=40,o=setInterval(async()=>{t++;try{(await fetch(`http://localhost:${G}`)).ok&&(clearInterval(o),K=!0,console.log(`\u2705 Vite dev server ready
|
|
5
|
+
`),e())}catch{t>=n&&(clearInterval(o),console.error("\u274C Vite failed to start after 20s"),process.exit(1))}},500)})}}else{const s=a(le,"../../studio-dist");l(s)?(g.use(ae.static(s)),console.log("\u{1F4E6} Production mode: Serving built files")):(console.error("\u274C Error: Studio files not found at:",s),console.error(" Build with: cd studio && npm run build:web"),process.exit(1))}function H(){const s=a(S,".zibby.config.mjs");return l(s)?s:a(T,".zibby.config.mjs")}function O(){H();const s=a(S,".zibby","output","sessions");return l(s)?s:a(T,".zibby","output","sessions")}function oe(s,e){let t=null,n=null,o=null;const i=a(s,"codegen");if(l(i)){const r=a(i,"test.spec.ts"),c=a(i,"generated-test.spec.js");t=l(r)?r:l(c)?c:null;const d=a(i,"test.selenium.py"),p=a(i,"generated-test-selenium.js");n=l(d)?d:l(p)?p:null;const m=a(i,"trace.zip");o=l(m)?m:null}const u=a(s,"generate_script","result.json");if(!t&&l(u))try{const c=JSON.parse(k(u,"utf8"))?.scriptPath;if(typeof c=="string"&&c.trim()){const d=c.trim(),p=d.startsWith("/")||process.platform==="win32"&&/^[A-Za-z]:[\\/]/.test(d)?d:a(e,d);l(p)&&(t=p)}}catch(r){console.warn("[Studio API] generate_script result.json:",r.message)}return!t&&!n&&!o?null:{playwrightFile:t,seleniumFile:n,tracePath:o}}function ze(s,e,t){try{ee(a(s,M),JSON.stringify({sessionId:e,pid:t??null,startedAt:Date.now()},null,2),"utf8")}catch(n){console.error("[Studio API] writeStudioRunMeta:",n.message)}}function D(s){try{const e=a(s,M);l(e)&&st(e)}catch{}}function De(s){const e=Number(s);if(!Number.isFinite(e)||e<=0)return[];try{const t=X(`pgrep -P ${e}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:524288}).trim();return t?t.split(/\n/).map(n=>parseInt(n.trim(),10)).filter(n=>Number.isFinite(n)&&n>0):[]}catch{return[]}}function Y(s,e){const t=Number(s);if(!Number.isFinite(t)||t<=0)return;const n=new Set;function o(i){if(!n.has(i)){n.add(i);for(const u of De(i))o(u);try{process.kill(i,e)}catch{}}}o(t)}function fe(s){const e=Number(s);if(!(!Number.isFinite(e)||e<=0))try{X(`taskkill /PID ${e} /T /F`,{stdio:"ignore",windowsHide:!0})}catch{}}function pe(s){if(!Number.isFinite(s)||s<=0)return;if(process.platform==="win32"){fe(s);return}Y(s,"SIGTERM");const e=setTimeout(()=>{Y(s,"SIGKILL")},800);typeof e.unref=="function"&&e.unref()}function Be(s,e){const n=R(s)||a(O(),s);try{Te(n,{recursive:!0}),ee(a(n,Fe),JSON.stringify({requestedAt:Date.now()}),"utf8")}catch(r){console.error("[Studio API] write studio stop request:",r.message)}const o=C.get(s);if(o){const r=o.pid;if(process.platform==="win32")fe(r);else{Y(r,"SIGTERM");const c=setTimeout(()=>{Y(r,"SIGKILL")},800);typeof c.unref=="function"&&c.unref()}return C.delete(s),D(n),!0}const i=a(n,M);if(l(i)){let r=null;try{const c=JSON.parse(k(i,"utf8"));r=Number(c.pid)}catch(c){console.error("[Studio API] studio-run.json read:",c.message)}if(D(n),Number.isFinite(r)&&r>0)return pe(r),!0}const u=Number(e);return Number.isFinite(u)&&u>0?(pe(u),D(n),!0):!1}function V(s){try{const e=s?.query?.sessionsRoot;if(typeof e!="string")return"";const t=e.trim();if(!t)return"";const n=decodeURIComponent(t),o=x(n);return l(o)?o:""}catch{return""}}function R(s,e=""){const t=typeof s=="string"?decodeURIComponent(s).trim():String(s||"").trim();if(!t)return null;const n=[t],o=t.split("_")[0]?.trim()||"";o&&o!==t&&n.push(o);const i=new Set,u=[],r=new Set,c=[],d=h=>{if(!h)return;const f=x(h);r.has(f)||(r.add(f),c.push(f))},p=h=>{if(h){d(h);for(const f of n){const y=a(h,f);i.has(y)||(i.add(y),u.push(y))}}};p(e),p(O()),p(a(T,".zibby","output","sessions"));let m;try{m=L(S)}catch{m=[]}for(const h of m){if(h.startsWith("."))continue;const f=a(S,h);try{if(!F(f).isDirectory())continue}catch{continue}p(a(f,".zibby","output","sessions"));let y;try{y=L(f,{withFileTypes:!0})}catch{y=[]}for(const _ of y)_?.isDirectory?.()&&String(_.name||"").startsWith(".zibby")&&p(a(f,_.name,"output","sessions"))}const N=u.filter(h=>l(h));if(N.length===0)for(const h of c){if(!l(h))continue;let f;try{f=L(h,{withFileTypes:!0})}catch{f=[]}for(const y of f){if(!y?.isDirectory?.())continue;const _=String(y.name||"");if(_===t||_===o||_.startsWith(`${t}_`)||o&&_.startsWith(`${o}_`)){const $=a(h,_);if(i.has($))continue;i.add($),N.push($)}}}if(N.length===0)return null;if(N.length===1)return N[0];const Z=h=>{let f=0;l(a(h,"execute_live"))&&(f+=20),Ae(h)&&(f+=15),l(a(h,"execute_live","events.json"))&&(f+=8),l(a(h,"execute_live","result.json"))&&(f+=6),l(a(h,M))&&(f+=4),l(a(h,".session-info.json"))&&(f+=2);try{const y=a(h,"execute_live");l(y)&&(f+=Math.min(F(y).mtimeMs/1e12,3))}catch{}return f};return N.sort((h,f)=>Z(f)-Z(h)),N[0]}function Ge(s,e){try{const t=typeof s=="string"?decodeURIComponent(s).trim():String(s||"").trim();if(!t||!e)return null;const n=(t.split("_")[0]||"").trim(),o=Ne(e);if(!n||!o||!l(o))return null;let i=[];try{i=L(o,{withFileTypes:!0})}catch{i=[]}let u=null;for(const r of i){if(!r?.isDirectory?.())continue;const c=String(r.name||"");if(!(c===t||c===n||c.startsWith(`${t}_`)||c.startsWith(`${n}_`)))continue;const d=a(o,c),p=ke(d);p&&(!u||Number(p.mtime||0)>Number(u.mtime||0))&&(u=p)}return u}catch{return null}}g.get("/api/config/check",(s,e)=>{const t=H();e.json({exists:l(t),path:t,isProjectLevel:t.startsWith(S)})}),g.get("/api/projects",(s,e)=>{try{const t=a(T,".zibby","config.json");if(!l(t))return e.json({projects:[]});const n=JSON.parse(k(t,"utf8")),o=typeof n.sessionToken=="string"&&n.sessionToken.trim()!==""?n.sessionToken.trim():"",i=Array.isArray(n.projects)?n.projects:[];if(!o)return e.json({projects:i});const u=String(Ee()).replace(/\/$/,"");fetch(`${u}/projects`,{headers:{Authorization:`Bearer ${o}`}}).then(r=>r.json().catch(()=>({})).then(c=>({ok:r.ok,status:r.status,body:c}))).then(({ok:r,body:c})=>{if(!r)return e.json({projects:i});const p=(Array.isArray(c?.projects)?c.projects:[]).map(m=>({name:m?.name,projectId:m?.projectId,apiToken:m?.apiToken||null,createdAt:m?.createdAt||null,updatedAt:m?.updatedAt||null}));n.projects=p;try{ee(t,`${JSON.stringify(n,null,2)}
|
|
6
|
+
`,"utf8")}catch{}return e.json({projects:p})}).catch(()=>e.json({projects:i}))}catch(t){e.status(500).json({error:t.message})}}),g.post("/api/projects/create",async(s,e)=>{try{const t=typeof s.body?.name=="string"?s.body.name.trim():"";if(!t)return e.status(400).json({error:"Project name is required"});const n=a(T,".zibby","config.json");if(!l(n))return e.status(401).json({error:"Not logged in"});const o=JSON.parse(k(n,"utf8")),i=typeof o.sessionToken=="string"&&o.sessionToken.trim()!==""?o.sessionToken.trim():"";if(!i)return e.status(401).json({error:"Not logged in"});const u=String(Ee()).replace(/\/$/,""),r=await fetch(`${u}/projects`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${i}`},body:JSON.stringify({name:t})}),c=await r.json().catch(()=>({}));if(!r.ok)return e.status(r.status).json({error:c.error||c.message||`HTTP ${r.status}`});const d=c?.project&&typeof c.project=="object"?c.project:null;if(d?.projectId){const m=(Array.isArray(o.projects)?o.projects:[]).filter(N=>String(N?.projectId||"")!==String(d.projectId));m.push({name:d.name||t,projectId:d.projectId,apiToken:d.apiToken||c?.apiToken||null,createdAt:d.createdAt||null,updatedAt:d.updatedAt||null}),o.projects=m;try{ee(n,`${JSON.stringify(o,null,2)}
|
|
7
|
+
`,"utf8")}catch{}}return e.json(c)}catch(t){return e.status(500).json({error:t.message||String(t)})}});function Ue(s,e){const t=Array.isArray(e)?e.filter(d=>typeof d=="string"):[],n=process.argv[1]?x(process.argv[1]):"",o=n?ot(n):"";let i;try{i=!!n&&l(n)&&F(n).isFile()&&!/\.(cmd|ps1|bat)$/i.test(n)&&(/\.(m|c)?js$/i.test(n)||o==="zibby")}catch{i=!1}if(i){const d=[n,"test",s,...t],p=[process.execPath,...d].map(m=>/\s/.test(String(m))?`"${String(m).replace(/"/g,'\\"')}"`:m).join(" ");return{useShell:!1,cmd:process.execPath,args:d,display:p}}const u=s.replace(/"/g,'\\"'),r=t.join(" "),c=`zibby test "${u}" ${r}`.trim();return{useShell:!0,cmd:c,args:[],display:c}}g.get("/api/run/result/:sessionId",(s,e)=>{const{sessionId:t}=s.params,n=U.get(t);if(!n)return e.status(404).json({pending:!0,error:"unknown_session"});e.json(n)}),g.post("/api/run",async(s,e)=>{const{task:t,args:n=[],sessionId:o,studioTestCaseId:i}=s.body;if(!t||!o)return e.status(400).json({success:!1,error:"task and sessionId are required"});const u=O(),r=a(u,o),c=x(r),d=i!=null&&String(i).trim()!==""?String(i).trim():String(o);U.set(o,{pending:!0});let p=!1,m=null;const N=()=>{if(m&&!m.destroyed)try{m.end()}catch{}m=null},Z=(f,y)=>{U.delete(o),N(),p||e.status(f).json(y)},h=()=>{const f=[];return E.clients.forEach(y=>{y.sessionId===o&&f.push(y)}),f};try{const f=Ue(t,n);console.log("[Studio API] Running:",f.display),f.useShell?console.warn('[Studio API] Falling back to shell "zibby" from PATH \u2014 if runs do nothing, run Studio from this repo or ensure `zibby` points to the same install.'):console.log("[Studio API] Using same CLI as this server (argv[1]):",process.argv[1]),Te(r,{recursive:!0}),m=Qe(a(r,ue),{flags:"w"}),B(c,{sessionId:String(o),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:S,outputBase:".zibby/output",sessionPathAbs:c});const y=Q(f.cmd,f.args,{cwd:S,shell:f.useShell,env:{...process.env,ZIBBY_SESSION_ID:o,ZIBBY_SESSION_PATH:x(r)}});C.set(o,y),ze(r,o,y.pid),B(c,{sessionId:String(o),studioTestCaseId:d,status:"running",runSource:"studio",activeStageIndex:0,activeNode:"preflight",cwd:S,outputBase:".zibby/output",sessionPathAbs:c,pid:y.pid??null});let _="",$="",ye=!1,Se=0;const Ie=setInterval(()=>{try{const b=Ae(r);if(!b||b.mtime<=Se)return;Se=b.mtime;const P=k(b.p).toString("base64"),v=b.p.toLowerCase().endsWith(".png")?"image/png":"image/jpeg";h().forEach(q=>{try{q.send(JSON.stringify({type:"video-frame",sessionId:o,frame:P,mime:v}))}catch{}})}catch{}},320),be=b=>{if(ye)return;ye=!0,N();const P={pending:!1,...b};typeof P.stdout=="string"&&P.stdout.length>J&&(P.stdout=`\u2026(truncated)
|
|
8
|
+
${P.stdout.slice(-J)}`),typeof P.stderr=="string"&&P.stderr.length>J&&(P.stderr=`\u2026(truncated)
|
|
9
|
+
${P.stderr.slice(-J)}`),U.set(o,P),h().forEach(v=>{try{v.send(JSON.stringify({type:"run-complete",sessionId:o,result:b}))}catch{}})};y.stdout.on("data",b=>{const P=b.toString();if(_+=P,m&&!m.destroyed)try{m.write(P)}catch(v){console.error("[Studio API] studio-cli.log write failed:",v.message)}h().forEach(v=>{try{v.send(JSON.stringify({type:"stdout",data:P}))}catch{}})}),y.stderr.on("data",b=>{const P=b.toString();if($+=P,m&&!m.destroyed)try{m.write(`[stderr] ${P}`)}catch(v){console.error("[Studio API] studio-cli.log write failed:",v.message)}h().forEach(v=>{try{v.send(JSON.stringify({type:"stderr",data:P}))}catch{}})}),y.on("error",b=>{clearInterval(Ie),C.delete(o),B(c,{sessionId:String(o),studioTestCaseId:d,status:"failed",runSource:"studio",activeNode:null,activeStageIndex:null,errorMessage:b.message,cwd:S,outputBase:".zibby/output",sessionPathAbs:c}),console.error("[Studio API] Spawn error:",b.message),be({heldUntilExit:!0,success:!1,exitCode:null,error:b.message,stderr:$,stdout:_,runName:null,videoPath:null,eventsPath:null,codegenFiles:null,metadata:{sessionId:o,task:t}})}),y.on("close",b=>{clearInterval(Ie),C.delete(o),B(c,{sessionId:String(o),studioTestCaseId:d,status:b===0?"completed":b===130||b===143?"interrupted":"failed",runSource:"studio",activeNode:null,activeStageIndex:null,exitCode:b,cwd:S,outputBase:".zibby/output",sessionPathAbs:c}),D(r),h().forEach(A=>{try{A.send(JSON.stringify({type:"exit",code:b}))}catch{}});const P=b===0;let v=null;const q=a(r,".session-info.json"),Pe=a(r,"result.json");if(l(q))try{const A=JSON.parse(k(q,"utf8"));v=A.name||A.task}catch(A){console.error("[Studio API] Failed to read session info:",A.message)}else if(l(Pe))try{const A=JSON.parse(k(Pe,"utf8"));v=A.name||A.task}catch(A){console.error("[Studio API] Failed to read result:",A.message)}const He=[/\[SessionRecorder\] Run name set: (.+)/,/Run name: (.+)/];if(!v)for(const A of He){const we=_.match(A);if(we){v=we[1].trim();break}}const Ye=oe(r,S),ie=Oe(r),ve=a(r,"events.json"),je=a(r,"execute_live","events.json"),ce=l(ve)?ve:l(je)?je:null;be({heldUntilExit:!0,success:P,exitCode:b,stdout:_,stderr:$,runName:v,videoPath:ie&&l(ie)?ie:null,eventsPath:ce&&l(ce)?ce:null,codegenFiles:Ye,metadata:{sessionId:o,task:t}})}),p=!0,e.status(202).json({accepted:!0,sessionId:o,pid:y.pid!=null&&Number.isFinite(Number(y.pid))?Number(y.pid):null,pollPath:`/api/run/result/${o}`,message:"Run started. Poll GET /api/run/result/:sessionId until pending is false."})}catch(f){Z(500,{success:!1,error:f.message})}}),g.post("/api/stop/:sessionId",(s,e)=>{const{sessionId:t}=s.params,n=s.body&&s.body.pid!=null?s.body.pid:null;if(Be(t,n))return e.json({success:!0});e.status(404).json({success:!1,error:"Session not found"})}),g.get("/api/recordings",(s,e)=>{try{const t=O();if(!l(t))return e.json({recordings:[],path:t});const o=L(t).map(i=>{const u=a(t,i),r=a(u,".session-info.json"),c=a(u,"result.json");let d={};return l(r)?d=JSON.parse(k(r,"utf8")):l(c)&&(d=JSON.parse(k(c,"utf8"))),{sessionId:i,...d,path:u}});e.json({path:t,recordings:o})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/cli-log",(s,e)=>{try{const{sessionId:t}=s.params;let n=parseInt(String(s.query.offset||"0"),10);(Number.isNaN(n)||n<0)&&(n=0);const o=V(s),i=R(t,o),u=a(i||a(O(),t),ue);if(!l(u))return e.json({exists:!1,content:"",size:0,fromByte:0,nextOffset:0});const r=F(u).size;n>r&&(n=r);let c=n,d=r-c;d>te&&(c=r-te,d=te);const p=Buffer.alloc(d),m=Xe(u,"r");try{et(m,p,0,d,c)}finally{tt(m)}e.json({exists:!0,content:p.toString("utf8"),size:r,fromByte:c,nextOffset:r})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/events",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.json({events:[]});let i=a(o,"events.json");if(!l(i)){const r=a(o,"execute_live","events.json");if(l(r))i=r;else return e.json({events:[]})}const u=JSON.parse(k(i,"utf8"));e.json({events:u})}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/live-preview",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.json({ok:!1,error:"session_not_found"});const u=ke(o)||Ge(t,o);if(!u)return e.json({ok:!1,error:"no_frame",sessionId:t,sessionPath:o});e.json({ok:!0,frame:u.base64,mime:u.mime,mtime:u.mtime,path:u.path})}catch(t){e.status(500).json({ok:!1,error:t.message})}}),g.get("/api/sessions/:sessionId/video",(s,e)=>{try{const{sessionId:t}=s.params,n=V(s),o=R(t,n);if(!o)return e.status(404).json({error:"Session not found"});const i=Oe(o);if(!i||!l(i))return e.status(404).json({error:"Video not found"});const u=F(i);e.writeHead(200,{"Content-Type":"video/webm","Content-Length":u.size}),qe(i).pipe(e)}catch(t){e.status(500).json({error:t.message})}}),g.get("/api/sessions/:sessionId/codegen/playwright",(s,e)=>{try{const{sessionId:t}=s.params,n=R(t)||a(O(),t);if(!l(n))return e.status(404).type("text/plain").send("Session not found");const o=oe(n,S);if(!o?.playwrightFile||!l(o.playwrightFile))return e.status(404).type("text/plain").send("No Playwright artifact");e.type("text/plain; charset=utf-8").send(k(o.playwrightFile,"utf8"))}catch(t){e.status(500).type("text/plain").send(t.message)}}),g.get("/api/sessions/:sessionId/codegen/selenium",(s,e)=>{try{const{sessionId:t}=s.params,n=R(t)||a(O(),t);if(!l(n))return e.status(404).type("text/plain").send("Session not found");const o=oe(n,S);if(!o?.seleniumFile||!l(o.seleniumFile))return e.status(404).type("text/plain").send("No Selenium artifact");e.type("text/plain; charset=utf-8").send(k(o.seleniumFile,"utf8"))}catch(t){e.status(500).type("text/plain").send(t.message)}}),g.post("/api/init",async(s,e)=>{const{agentType:t,apiKey:n}=s.body;try{const o=`zibby init --agent ${t} --headed`,i=Q(o,[],{cwd:S,env:{...process.env,CURSOR_API_KEY:t==="cursor"?n:process.env.CURSOR_API_KEY,ANTHROPIC_API_KEY:t==="claude"?n:process.env.ANTHROPIC_API_KEY,OPENAI_API_KEY:t==="codex"?n:process.env.OPENAI_API_KEY,GEMINI_API_KEY:t==="gemini"?n:process.env.GEMINI_API_KEY,GOOGLE_API_KEY:t==="gemini"?n:process.env.GOOGLE_API_KEY,CI:"true",ZIBBY_CI:"true"},shell:!0});let u="",r="";i.stdout.on("data",p=>{u+=p.toString()}),i.stderr.on("data",p=>{r+=p.toString()});let c=!1;const d=(p,m)=>{if(!c)if(c=!0,p===0)e.json({success:!0,stdout:u});else{const N=m||r||"zibby init failed";e.status(500).json({success:!1,error:N,stdout:u})}};i.on("close",p=>d(p)),i.on("error",p=>d(1,p.message))}catch(o){e.status(500).json({success:!1,error:o.message})}});const ne=(s,e)=>{const t={type:s,message:e,time:new Date().toLocaleTimeString()};E.clients.forEach(n=>{if(n.listenType==="console")try{n.send(JSON.stringify(t))}catch{}})},We=console.log,Je=console.error,Me=console.warn,re=s=>s.map(e=>{if(e==null)return String(e);if(typeof e=="object")try{return rt(e,{depth:4,colors:!1,breakLength:100})}catch{return String(e)}return String(e)}).join(" ");console.log=(...s)=>{We(...s),ne("info",re(s))},console.error=(...s)=>{Je(...s),ne("error",re(s))},console.warn=(...s)=>{Me(...s),ne("warn",re(s))},E.on("connection",(s,e)=>{const t=e.url;if(t.includes("/console"))s.listenType="console",console.log("[WebSocket] Console listener connected"),s.send(JSON.stringify({type:"success",message:"\u{1F3AD} Connected to Studio server logs",time:new Date().toLocaleTimeString()}));else{let n=t.split("/").pop()||"";try{n=decodeURIComponent(n)}catch{}s.sessionId=n,s.listenType="session",console.log(`[WebSocket] Session listener connected: ${n}`)}s.on("close",()=>{s.listenType==="console"?console.log("[WebSocket] Console listener disconnected"):console.log(`[WebSocket] Session listener disconnected: ${s.sessionId}`)})}),g.get("/api/workflow/graph",async(s,e)=>{try{const t=a(S,".zibby","graph.mjs");if(!l(t))return console.warn("[Workflow Graph API] Graph file not found at",t),e.json({graph:null,error:"No graph.mjs found in .zibby folder"});const n=await import(`${ct(t).href}?t=${Date.now()}`),o=n.default||Object.values(n)[0];if(!o||typeof o!="function")return console.error("[Workflow Graph API] Invalid graph module, got:",typeof o),e.json({graph:null,error:"Invalid graph module"});const u=new o().buildGraph(),r=u.toJSON?u.toJSON():u.serialize();console.log(`[Workflow Graph API] graph OK nodes=${r.nodes?.length??0} edges=${r.edges?.length??0} (${t})`),e.json({graph:r})}catch(t){console.error("[Workflow Graph API] Failed to load workflow graph:",t),e.status(500).json({graph:null,error:t.message})}}),K||g.get("*",(s,e)=>{const t=a(le,"../../studio-dist/index.html");l(t)?e.sendFile(t):e.status(404).send("Studio not built. Run: cd studio && npm run build:web")});const Ke=()=>{K?(console.log(`
|
|
10
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
11
|
+
\u2551 \u{1F525} Zibby Studio (DEV MODE) \u2551
|
|
12
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
13
|
+
|
|
14
|
+
Frontend: http://localhost:${G} (with hot reload)
|
|
15
|
+
API: http://localhost:${w}/api
|
|
16
|
+
Project: ${S}
|
|
17
|
+
Config: ${H()}
|
|
18
|
+
|
|
19
|
+
\u26A1 Hot reload enabled
|
|
20
|
+
${W?"\u{1F680} Vite auto-started":"\u2705 Using existing Vite server"}
|
|
21
|
+
Press Ctrl+C to stop
|
|
22
|
+
`),console.log("[Studio] POST /api/run returns 202 immediately; poll GET /api/run/result/:sessionId (avoids gateway timeouts on long runs)."),j.open!==!1&&(j.web?_e(`http://localhost:${G}`):de().catch(s=>{console.error(`[Studio] Failed to launch desktop app: ${s.message}`),console.error("[Studio] Run with --web to open browser instead.")}))):(console.log(`
|
|
23
|
+
\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
|
|
24
|
+
\u2551 \u{1F3AD} Zibby Studio Web \u2551
|
|
25
|
+
\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
|
|
26
|
+
|
|
27
|
+
Local: http://localhost:${w}
|
|
28
|
+
Project: ${S}
|
|
29
|
+
Config: ${H()}
|
|
30
|
+
|
|
31
|
+
Press Ctrl+C to stop
|
|
32
|
+
`),console.log("[Studio] POST /api/run returns 202 immediately; poll GET /api/run/result/:sessionId (avoids gateway timeouts on long runs)."),j.open!==!1&&(j.web?_e(`http://localhost:${w}`):de().catch(s=>{console.error(`[Studio] Failed to launch desktop app: ${s.message}`),console.error("[Studio] Run with --web to open browser instead.")})))};let me=!1;function ge(){const s=async e=>{if(I.removeListener("error",s),e.code==="EADDRINUSE"&&!me){me=!0,console.log(`[Studio] Port ${w} still busy \u2014 forcing cleanup and retrying...`),await Ce(w),ge();return}console.error("[Studio] Server error:",e.message),process.exit(1)};I.once("error",s),I.listen(w,()=>{I.removeListener("error",s),I.on("error",e=>{console.error("[Studio] Runtime error:",e.message)}),Ke()})}await Ce(w),ge();const he=()=>{console.log(`
|
|
33
|
+
Stopping Studio...`);const s=O();for(const e of C.keys()){const t=x(a(s,e));try{B(t,{status:"interrupted",runSource:"studio",activeNode:null,activeStageIndex:null,exitReason:"studio-shutdown"})}catch{}try{D(a(s,e))}catch{}}W&&(W.kill(),console.log("Stopped Vite dev server")),C.forEach(e=>e.kill()),I.close(()=>{console.log("Studio stopped"),process.exit(0)})};process.on("SIGINT",he),process.on("SIGTERM",he)}export{Rt as studioCommand};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import{readFileSync as u,existsSync as s,statSync as v}from"fs";import{join as c,basename as E}from"path";import{glob as P}from"glob";import e from"chalk";import T from"ora";import _ from"dotenv";import{getApiUrl as S,getCurrentEnvironment as j,getFrontendUrl as A}from"../config/environments.js";import{getSessionToken as k,getUserInfo as D}from"../config/config.js";_.config();function C(a,t){const i=t?.paths?.specs||"test-specs",r=t?.paths?.generated||"tests";return a.replace(i,r).replace(/\.txt$/,".spec.js")}async function L(a,t){const i=j();console.log(e.bold.cyan(`
|
|
2
|
+
\u{1F4E4} Zibby Cloud Upload
|
|
3
|
+
`)),console.log(e.gray("\u2501".repeat(50))),console.log(e.white(`Environment: ${e.cyan(i.name)}
|
|
4
|
+
`));const r=process.cwd(),$=t.project||process.env.ZIBBY_API_KEY;$||(console.log(e.red(`\u274C Error: Project API key required
|
|
5
|
+
`)),console.log(e.white("Provide via:")),console.log(e.gray(" --project <token>")),console.log(e.gray(` or ZIBBY_API_KEY in .env
|
|
6
|
+
`)),process.exit(1));const U=process.env.ZIBBY_USER_TOKEN||k(),b=D();U||(console.log(e.red(`
|
|
7
|
+
\u274C Error: User authentication required
|
|
8
|
+
`)),console.log(e.cyan("Option 1 (Local):")),console.log(e.gray(` zibby login
|
|
9
|
+
`)),console.log(e.cyan("Option 2 (CI/CD):")),console.log(e.gray(` export ZIBBY_USER_TOKEN=zby_pat_xxxxx
|
|
10
|
+
`)),process.exit(1)),console.log(b?e.gray(`Authenticated: ${b.email}
|
|
11
|
+
`):e.gray(`Authenticated: Using Personal Access Token
|
|
12
|
+
`));const I=P.sync("test-results/sessions/*",{cwd:r}).filter(o=>v(c(r,o)).isDirectory()&&!/session_/.test(o)).sort((o,p)=>{const l=v(c(r,o));return v(c(r,p)).mtimeMs-l.mtimeMs});I.length===0&&(console.log(e.red(`\u274C No test results found
|
|
13
|
+
`)),console.log(e.white(`Run a test first: zibby run <spec-path>
|
|
14
|
+
`)),process.exit(1));const f=I[0],d=c(r,f,"execute_live");s(d)||(console.log(e.red(`\u274C No execution data found in ${f}
|
|
15
|
+
`)),process.exit(1));const m=c(d,"recording.webm"),y=c(d,"events.json"),w=c(d,"title.txt"),x=C(a,{});console.log(e.white("Found artifacts:")),console.log(e.gray(` Session: ${E(f)}`)),console.log(e.gray(` Video: ${s(m)?"\u2713":"\u2717"}`)),console.log(e.gray(` Events: ${s(y)?"\u2713":"\u2717"}`)),console.log(e.gray(` Test: ${s(x)?"\u2713":"\u2717"}
|
|
16
|
+
`));const h=T(`Uploading to ${i.name}...`).start();try{const o=new FormData;if(o.append("specPath",a),o.append("agent",t.agent||"cursor"),o.append("agentType",t.agent||"cursor"),t.collection&&o.append("collectionIdOrName",t.collection),t.folder&&o.append("folder",t.folder),s(m)){const n=new Blob([u(m)]);o.append("video",n,"recording.webm")}if(s(y)){const n=u(y,"utf-8");o.append("events",n)}if(s(x)){const n=u(x,"utf-8");o.append("testCode",n)}if(s(w)){const n=u(w,"utf-8").trim();o.append("title",n)}const p=S(),l=await fetch(`${p}/executions/upload`,{method:"POST",headers:{Authorization:`Bearer ${$}`,"X-User-Token":U},body:o});if(!l.ok){const n=await l.text();h.fail(`Upload failed: ${l.status}`),console.log(e.red(`${n}
|
|
17
|
+
`)),process.exit(1)}const g=await l.json();h.succeed("Upload complete!"),console.log(e.green(`
|
|
18
|
+
\u2713 Test uploaded successfully`)),console.log(e.gray(` Execution ID: ${g.executionId}`));const B=`${A()}/projects/${g.projectId}/runs/${g.executionId}`;console.log(e.cyan(`
|
|
19
|
+
View results: ${B}
|
|
20
|
+
`))}catch(o){h.fail("Upload failed"),console.log(e.red(`
|
|
21
|
+
${o.message}
|
|
22
|
+
`)),process.exit(1)}}export{L as uploadCommand};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{organizeVideos as s}from"@zibby/core";import{resolve as i}from"path";import r from"chalk";import t from"ora";async function m(n){const e=t("Organizing test videos...").start();try{const o=await s({projectRoot:i(process.cwd()),verbose:!1});o.success?(e.succeed(`Organized ${o.movedCount} video(s)`),console.log(r.gray(`
|
|
2
|
+
\u{1F4C2} Videos are now next to their test files in tests/
|
|
3
|
+
`))):(e.fail("Failed to organize videos"),o.error&&console.log(r.red(`Error: ${o.error}
|
|
4
|
+
`)))}catch(o){e.fail("Failed to organize videos"),console.log(r.red(`
|
|
5
|
+
\u274C Error: ${o.message}
|
|
6
|
+
`)),process.exit(1)}}export{m as videoCommand};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import{readFileSync as A,writeFileSync as v,existsSync as b,mkdirSync as z}from"fs";import{resolve as J,join as y}from"path";import o from"chalk";import j from"ora";import P from"dotenv";import{getApiUrl as E,getCurrentEnvironment as x}from"../config/environments.js";import{validateGraphConfig as _}from"@zibby/core/framework/graph-compiler.js";import{generateWorkflowCode as N,generateNodeConfigsJson as B}from"@zibby/core/framework/code-generator.js";import"@zibby/core/templates/register-nodes.js";P.config();function C(s){const e=s.apiKey||process.env.ZIBBY_API_KEY;e||(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 f=s.project||process.env.ZIBBY_PROJECT_ID;return f||(console.log(o.red(`
|
|
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 k=["analysis","implementation","run_test"];function D(s){const e=s.type;return e||(console.log(o.red(`
|
|
6
|
+
--type is required`)),console.log(o.gray(` Valid types: ${k.join(", ")}
|
|
7
|
+
`)),process.exit(1)),k.includes(e)||(console.log(o.red(`
|
|
8
|
+
Invalid workflow type: "${e}"`)),console.log(o.gray(` Valid types: ${k.join(", ")}
|
|
9
|
+
`)),process.exit(1)),e}async function R(s){const e=x(),{apiKey:f,projectId:d}=C(s),n=D(s);console.log(o.bold.cyan(`
|
|
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=j(" Fetching workflow from cloud...").start();try{const p=E(),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
|
+
`)),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
|
+
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
|
+
`)),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
|
+
${p.message}
|
|
16
|
+
`)),process.exit(1)}}function F(s,e,f){const d=process.cwd(),n=f.output||y(d,".zibby");b(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=N(e.graph,l);v(r,t,"utf-8");const i=e.graph.nodeConfigs||{},a=B(i),w=`workflow-${s}.config.json`,$=y(n,w);v($,`${JSON.stringify(a,null,2)}
|
|
17
|
+
`,"utf-8");const m=`workflow-${s}.json`,c=y(n,m),g={_meta:{...l,downloadedAt:new Date().toISOString()},...e.graph};v(c,`${JSON.stringify(g,null,2)}
|
|
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($)}`)),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
|
+
`)),console.log(o.white(" To run locally:")),console.log(o.cyan(` zibby analyze --workflow ${r}
|
|
21
|
+
`)),console.log(o.white(" To upload changes back:")),console.log(o.cyan(` zibby workflow upload --project ${e.projectId} --type ${s}
|
|
22
|
+
`))}async function q(s){const e=x(),{apiKey:f,projectId:d}=C(s),n=D(s);console.log(o.bold.cyan(`
|
|
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||(b(r)?r:p);b(t)||(console.log(o.red(`
|
|
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=j(" Loading JS workflow module...").start();try{const{pathToFileURL:g}=await import("url"),u=await import(g(J(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
|
+
${g.message}
|
|
28
|
+
`)),process.exit(1)}}else{let c;try{c=JSON.parse(A(t,"utf-8"))}catch(h){console.log(o.red(`
|
|
29
|
+
Failed to parse ${t}: ${h.message}
|
|
30
|
+
`)),process.exit(1)}const{_meta:g,...u}=c;a=u}(!a.nodes||!a.edges)&&(console.log(o.red(`
|
|
31
|
+
Invalid workflow file: missing nodes or edges`)),console.log(o.gray(` The file should contain { nodes: [...], edges: [...], nodeConfigs: {...} }
|
|
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=j(" Validating graph...").start(),$=_(a);if(!$.valid){w.fail(" Graph validation failed"),console.log("");for(const c of $.errors)console.log(o.red(` ${c}`));console.log(o.gray(`
|
|
34
|
+
Fix the errors above and try again.
|
|
35
|
+
`)),process.exit(1)}w.succeed(" Graph is valid");const m=j(" Uploading to cloud...").start();try{const c=E(),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();m.fail(` API error: ${g.status}`),console.log(o.red(` ${h}
|
|
36
|
+
`)),process.exit(1)}const u=await g.json();m.succeed(` Uploaded successfully (v${u.version})`),console.log(o.green(`
|
|
37
|
+
Workflow "${n}" updated to version ${u.version}`)),console.log(o.gray(` Project: ${d}
|
|
38
|
+
`))}catch(c){m.fail(" Upload failed"),console.log(o.red(`
|
|
39
|
+
${c.message}
|
|
40
|
+
`)),process.exit(1)}}async function H(s){const e=x(),{apiKey:f,projectId:d}=C(s);console.log(o.bold.cyan(`
|
|
41
|
+
Zibby Workflows
|
|
42
|
+
`));const n=j(" Fetching workflows...").start();try{const l=E(),p=[];for(const r of k){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
|
+
`),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
|
+
${l.message}
|
|
45
|
+
`)),process.exit(1)}}export{R as workflowDownloadCommand,H as workflowListCommand,q as workflowUploadCommand};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{existsSync as i,mkdirSync as l,readFileSync as x,writeFileSync as p}from"fs";import{homedir as c}from"os";import{join as t}from"path";function s(){return process.env.ZIBBY_CONFIG_DIR||t(c(),".zibby")}function f(){return t(s(),"config.json")}const u=t(c(),".zibby"),y=t(u,"config.json");function m(){const n=s();i(n)||l(n,{recursive:!0})}function e(){try{const n=f();if(i(n)){const o=x(n,"utf-8");return JSON.parse(o)}}catch{}return{}}function r(n){m(),p(f(),JSON.stringify(n,null,2))}function g(){return e().sessionToken||null}function a(n){const o=e();o.sessionToken=n,r(o)}function d(){return e().user||null}function U(n){const o=e();o.user=n,r(o)}function I(){return g()!==null}function P(){const n=e();delete n.sessionToken,delete n.user,delete n.mem0ProxyUrl,r(n)}function S(){return e().proxyUrl||null}function v(n){const o=e();o.proxyUrl=n,r(o)}function C(){return e().mem0ProxyUrl||null}function j(n){const o=e();o.mem0ProxyUrl=n,r(o)}function F(){return e().projects||[]}function k(n){const o=e();o.projects=n,r(o)}var b={loadConfig:e,saveConfig:r,getSessionToken:g,saveSessionToken:a,getUserInfo:d,saveUserInfo:U,isLoggedIn:I,clearSession:P,getProxyUrl:S,saveProxyUrl:v,getMem0ProxyUrl:C,saveMem0ProxyUrl:j,getProjects:F,saveProjects:k,CONFIG_DIR:u,CONFIG_FILE:y};export{P as clearSession,b as default,C as getMem0ProxyUrl,F as getProjects,S as getProxyUrl,g as getSessionToken,d as getUserInfo,I as isLoggedIn,e as loadConfig,r as saveConfig,j as saveMem0ProxyUrl,k as saveProjects,v as saveProxyUrl,a as saveSessionToken,U as saveUserInfo};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const n={local:{name:"Local Development",apiUrl:"http://localhost:3001",accountApiUrl:"http://localhost:3001",frontendUrl:"http://localhost:3000",description:"Local backend running on port 3001"},prod:{name:"Production",apiUrl:process.env.ZIBBY_PROD_API_URL||"https://api-prod.zibby.app",accountApiUrl:process.env.ZIBBY_PROD_ACCOUNT_API_URL||"https://account-api-prod.zibby.app",frontendUrl:process.env.ZIBBY_PROD_FRONTEND_URL||"https://studio.zibby.app",description:"Production environment"}};function s(){let o;if(process.env.ZIBBY_API_URL)o=process.env.ZIBBY_API_URL;else{const r=process.env.ZIBBY_ENV||"prod";n[r]?o=n[r].apiUrl:o=n.prod.apiUrl}try{const r=new URL(o);return r.protocol!=="http:"&&r.protocol!=="https:"?(console.error(`\u26A0\uFE0F Invalid API URL protocol: ${r.protocol} (only http/https allowed)`),n.prod.apiUrl):o}catch{return console.error(`\u26A0\uFE0F Invalid API URL: ${o}`),n.prod.apiUrl}}function c(){if(process.env.ZIBBY_ACCOUNT_API_URL)return process.env.ZIBBY_ACCOUNT_API_URL;const o=process.env.ZIBBY_ENV||"prod";return(n[o]||n.prod).accountApiUrl}function l(){const o=process.env.ZIBBY_ENV||"prod";return n[o]||n.prod}function i(){let o;if(process.env.ZIBBY_FRONTEND_URL)o=process.env.ZIBBY_FRONTEND_URL;else{const r=process.env.ZIBBY_ENV||"prod";n[r]?o=n[r].frontendUrl:o=n.prod.frontendUrl}try{const r=new URL(o);if(r.protocol!=="http:"&&r.protocol!=="https:")return console.error(`\u26A0\uFE0F Invalid frontend URL protocol: ${r.protocol} (only http/https allowed)`),n.local.frontendUrl;if(process.env.NODE_ENV==="production"||process.env.ZIBBY_ENV==="prod"){const p=["zibby.app","studio.zibby.app","studio-staging.zibby.app"],t=r.hostname;if(!p.some(e=>t===e||t.endsWith(`.${e}`))&&!t.includes("localhost")&&t!=="127.0.0.1")return console.error(`\u26A0\uFE0F Untrusted frontend URL in production: ${t}`),"https://studio.zibby.app"}return o}catch{return console.error(`\u26A0\uFE0F Invalid frontend URL: ${o}`),n.local.frontendUrl}}export{n as ENVIRONMENTS,c as getAccountApiUrl,s as getApiUrl,l as getCurrentEnvironment,i as getFrontendUrl};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import{existsSync as l,mkdirSync as F,readFileSync as S,readdirSync as g,unlinkSync as p,writeFileSync as d}from"fs";import{join as f}from"path";import{execSync as N}from"child_process";import{DEFAULT_OUTPUT_BASE as P}from"@zibby/core";function a(t){return t?.paths?.output||P}function m(t,r,i){return f(t,r,`.zibby-chat-run-pids-${i}.json`)}function b(t,r,i){const e=m(t,r,i);if(!l(e))return[];try{const n=JSON.parse(S(e,"utf8"));return(Array.isArray(n?.pids)?n.pids:[]).map(o=>Number(o)).filter(o=>Number.isFinite(o)&&o>0)}catch{return[]}}function C(t,r,i,e={}){const n=Number(r),s=Number(i);if(!Number.isFinite(n)||n<=0||!Number.isFinite(s)||s<=0)return;const o=a(e),c=f(t,o);F(c,{recursive:!0});const u=b(t,o,n);u.includes(s)||u.push(s),d(m(t,o,n),`${JSON.stringify({v:1,pids:u})}
|
|
2
|
+
`,"utf8")}function R(t,r,i,e={}){const n=Number(r),s=Number(i);if(!Number.isFinite(n)||n<=0||!Number.isFinite(s)||s<=0)return;const o=a(e),c=m(t,o,n);if(!l(c))return;const u=b(t,o,n).filter(h=>h!==s);if(u.length===0)try{p(c)}catch{}else d(c,`${JSON.stringify({v:1,pids:u})}
|
|
3
|
+
`,"utf8")}function k(t){const r=Number(t);if(!Number.isFinite(r)||r<=0)return[];try{const i=N(`pgrep -P ${r}`,{encoding:"utf8",stdio:["ignore","pipe","ignore"],maxBuffer:524288}).trim();return i?i.split(/\n/).map(e=>parseInt(e.trim(),10)).filter(e=>Number.isFinite(e)&&e>0):[]}catch{return[]}}function y(t,r){const i=Number(t);if(!Number.isFinite(i)||i<=0)return;const e=new Set;function n(s){if(!e.has(s)){e.add(s);for(const o of k(s))n(o);try{process.kill(s,r)}catch{}}}n(i)}function x(t){const r=Number(t);if(!(!Number.isFinite(r)||r<=0))try{N(`taskkill /PID ${r} /T /F`,{stdio:"ignore",windowsHide:!0})}catch{}}function T(t){const r=Number(t);if(!Number.isFinite(r)||r<=0)return;if(process.platform==="win32"){x(r);return}y(r,"SIGTERM");const i=setTimeout(()=>{y(r,"SIGKILL")},800);typeof i.unref=="function"&&i.unref()}function U(t,r,i={}){const e=Number(r);if(!Number.isFinite(e)||e<=0)return;const n=a(i),s=m(t,n,e),o=b(t,n,e);for(const c of o)c!==process.pid&&T(c);try{l(s)&&p(s)}catch{}}function A(t){try{return process.kill(t,0),!0}catch{return!1}}const I=/^\.zibby-chat-run-pids-(\d+)\.json$/;function w(t,r={}){const i=a(r),e=f(t,i);let n;try{n=g(e)}catch{return}for(const s of n){const o=I.exec(s);if(!o)continue;const c=Number(o[1]);if(!(!Number.isFinite(c)||c<=0)&&!A(c))try{p(f(e,s))}catch{}}}export{w as cleanupStalePidFiles,U as killAllChatOrchestratedRuns,T as killPidTreeBestEffort,C as registerChatOrchestratedRun,R as unregisterChatOrchestratedRun};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
async function s(n,i){const t=process.env.CONTEXT_PRESIGNED_URL;if(!t)throw new Error("CONTEXT_PRESIGNED_URL env var is required");console.log("\u{1F4E6} Fetching execution context via pre-signed URL");const o=await fetch(t);if(!o.ok)throw new Error(`Failed to fetch execution context: ${o.status}`);const e=await o.json();return console.log(` \u2705 Got ticketContext (${JSON.stringify(e.ticketContext||{}).length} chars)`),e.nodeConfigs&&Object.keys(e.nodeConfigs).length>0&&console.log(` \u2705 Got nodeConfigs (${Object.keys(e.nodeConfigs).length} nodes configured)`),{ticketContext:e.ticketContext||{},nodeConfigs:e.nodeConfigs||{},graphConfig:e.graphConfig||null,repos:e.repos||[]}}export{s as fetchExecutionContext};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{SQSClient as _,SendMessageCommand as u}from"@aws-sdk/client-sqs";let E=null;function O(){return E||(E=new _({region:process.env.AWS_REGION||"ap-southeast-2"})),E}async function $(c,e,s,o){const{EXECUTION_ID:t,SQS_AUTH_TOKEN:r,PROGRESS_API_URL:n,PROGRESS_QUEUE_URL:S,PROJECT_API_TOKEN:l}=o;if(!t)return;const a={executionId:t,...r&&{sqsAuthToken:r},step:{name:c,status:e,logs:s,timestamp:new Date().toISOString(),...e==="success"&&{completedAt:new Date().toISOString()}},status:e==="failed"?"failed":"running"};try{n?await f(n,t,a,l):S&&await p(S,t,a)}catch(d){console.error(`\u26A0\uFE0F Failed to send progress: ${d.message}`)}}async function m(c,e,s){const{EXECUTION_ID:o,SQS_AUTH_TOKEN:t,PROGRESS_API_URL:r,PROGRESS_QUEUE_URL:n,PROJECT_API_TOKEN:S}=c;if(!o||!s)return;const l=JSON.stringify(s).length;console.log(`\u{1F4E6} Sending artifact: ${e} (${(l/1024).toFixed(1)}KB)`);const a={executionId:o,...t&&{sqsAuthToken:t},artifacts:{[e]:s},timestamp:new Date().toISOString()},d=r?"HTTP":n?"SQS":"NONE",i=JSON.stringify(a).length;try{if(r)await f(r,o,a,S);else if(n)await p(n,o,a);else{console.warn(`\u26A0\uFE0F No transport configured for artifact ${e} \u2014 neither PROGRESS_API_URL nor PROGRESS_QUEUE_URL set`);return}console.log(`\u2705 Artifact ${e} sent via ${d} (payload=${(i/1024).toFixed(1)}KB, value=${(l/1024).toFixed(1)}KB)`)}catch(g){console.error(`\u274C Failed to send artifact ${e} via ${d}:`),console.error(` Payload size: ${(i/1024).toFixed(1)}KB, Value size: ${(l/1024).toFixed(1)}KB`),console.error(` Error: ${g.message}`),g.name&&console.error(` Error type: ${g.name}`),g.code&&console.error(` Error code: ${g.code}`),i>256*1024&&console.error(" \u26A0\uFE0F Message exceeds SQS 256KB limit! Consider splitting or compressing.")}}async function P(c,{status:e,error:s}){const{EXECUTION_ID:o,SQS_AUTH_TOKEN:t,PROGRESS_API_URL:r,PROGRESS_QUEUE_URL:n,PROJECT_API_TOKEN:S}=c;if(!o)return;const l={executionId:o,...t&&{sqsAuthToken:t},status:e,...s&&{error:s},timestamp:new Date().toISOString()},a=r?"HTTP":n?"SQS":"NONE",d=JSON.stringify(l).length;console.log(`\u{1F4CA} Sending final status: ${e} via ${a} (${(d/1024).toFixed(1)}KB)`);try{if(r)await f(r,o,l,S);else if(n){const i=["completed","failed","insufficient_context","blocked"].includes(e)?"execution_completed":"progress_update";await p(n,o,l,i)}else{console.warn("\u26A0\uFE0F No transport configured for final status \u2014 neither PROGRESS_API_URL nor PROGRESS_QUEUE_URL set");return}console.log(`\u2705 Final status ${e} sent via ${a}`)}catch(i){console.error(`\u274C Failed to send final status (${e}) via ${a}:`),console.error(` Payload: ${(d/1024).toFixed(1)}KB`),console.error(` Error: ${i.message}`),i.name&&console.error(` Error type: ${i.name}`),i.code&&console.error(` Error code: ${i.code}`)}}async function f(c,e,s,o){const t=`${c}/${e}/progress`,r={"Content-Type":"application/json"};o&&(r.Authorization=`Bearer ${o}`);const n=await fetch(t,{method:"POST",headers:r,body:JSON.stringify(s)});if(!n.ok){const S=await n.text();throw new Error(`HTTP ${n.status}: ${S}`)}}async function p(c,e,s,o="progress_update"){const t=JSON.stringify(s),r=(t.length/1024).toFixed(1);t.length>256*1024&&console.error(`\u274C SQS message too large: ${r}KB (limit 256KB) for ${e} [${o}]`),await O().send(new u({QueueUrl:c,MessageBody:t,MessageGroupId:e,MessageAttributes:{executionId:{DataType:"String",StringValue:e},messageType:{DataType:"String",StringValue:o}}}))}export{m as reportArtifact,P as reportFinalStatus,$ as reportProgress};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createWriteStream as l,mkdirSync as d}from"fs";import{join as p,isAbsolute as w}from"path";const m="studio-cli.log";function a(t,o){if(!t||typeof t!="string")return null;const r=t.trim();return r?w(r)?r:p(o,r):null}function S(t){if(!t)return()=>{};d(t,{recursive:!0});const o=p(t,m),r=l(o,{flags:"a"}),e=process.stdout.write.bind(process.stdout),s=process.stderr.write.bind(process.stderr),i=f=>function(u,n,c){typeof n=="function"&&(c=n,n=void 0);try{Buffer.isBuffer(u)?r.write(u):r.write(String(u),n||"utf8")}catch{}return f(u,n,c)};return process.stdout.write=i(e),process.stderr.write=i(s),()=>{process.stdout.write=e,process.stderr.write=s;try{r.end()}catch{}}}function C(t,o,r){let e=null;function s(i){if(!e&&i?.sessionPath){const f=a(i.sessionPath,o);f&&(e=S(f))}typeof t=="function"&&t(i)}return s.dispose=()=>{typeof e=="function"&&(e(),e=null)},s}export{C as composePipelineProgressWithStudioCliLog,S as startStudioCliLogMirror};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import e from"os";import i from"path";import{existsSync as u,readFileSync as g,createWriteStream as h,mkdirSync as y,rmSync as S}from"fs";import{extract as b}from"tar";import x from"adm-zip";import a from"chalk";const $=process.env.ZIBBY_STUDIO_CDN||"https://cdn.zibby.app/studio/latest",r=i.join(e.homedir(),".zibby","studio");function v(){const t=e.platform(),o=e.arch();if(t==="darwin")return`darwin-${o}.tar.gz`;if(t==="win32")return`win32-${o}.zip`;throw new Error(`Unsupported platform: ${t} ${o}`)}function z(){const t=v();return`${$}/${t}`}async function A(){const t=z(),o=i.basename(t);console.log(a.cyan(`
|
|
2
|
+
\u{1F4E6} Downloading Zibby Studio...`)),console.log(a.gray(` ${t}
|
|
3
|
+
`));try{const n=await fetch(t);if(!n.ok)throw new Error(`Download failed: ${n.status} ${n.statusText}`);const s=parseInt(n.headers.get("content-length")||"0",10),c=i.join(e.tmpdir(),o);let l=0;const d=h(c);for await(const p of n.body)if(l+=p.length,d.write(p),s>0){const f=(l/s*100).toFixed(1),m=(l/1024/1024).toFixed(1),w=(s/1024/1024).toFixed(1);process.stdout.write(`\r \u{1F4E5} ${f}% (${m}MB / ${w}MB)`)}return d.end(),console.log(`
|
|
4
|
+
`),u(r)&&(console.log(a.gray(" \u{1F5D1}\uFE0F Removing old version...")),S(r,{recursive:!0,force:!0})),y(r,{recursive:!0}),console.log(a.cyan(" \u{1F4C2} Extracting...")),c.endsWith(".tar.gz")?await D(c,r):c.endsWith(".zip")&&await I(c,r),console.log(a.green(` \u2705 Zibby Studio installed!
|
|
5
|
+
`)),!0}catch(n){throw console.log(a.red(`
|
|
6
|
+
\u274C Installation failed: ${n.message}
|
|
7
|
+
`)),n}}async function D(t,o){await b({file:t,cwd:o,strip:1})}async function I(t,o){new x(t).extractAllTo(o,!0)}function _(){const t=Z();return!!(t&&u(t))}function Z(){return e.platform()==="darwin"?i.join(r,"Zibby Studio.app"):e.platform()==="win32"?i.join(r,"Zibby Studio.exe"):null}function E(){const t=i.join(r,"version.txt");return u(t)?g(t,"utf-8").trim():"unknown"}export{Z as getStudioAppPath,E as getStudioVersion,A as installStudio,_ as isStudioInstalled};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{spawn as i}from"child_process";import s from"os";import{getStudioAppPath as e}from"./studio-installer.js";function f(r={}){const o=e();if(!o)throw new Error("Studio not installed");const t=[];r.port&&t.push("--port",String(r.port)),r.sessionsDir&&t.push("--sessions",r.sessionsDir),s.platform()==="darwin"?i("open",[o,"--args",...t],{detached:!0,stdio:"ignore"}).unref():s.platform()==="win32"&&i(o,t,{detached:!0,stdio:"ignore"}).unref()}export{f as launchStudio};
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zibby/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.27",
|
|
4
4
|
"description": "Zibby CLI - Test automation generator and runner",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"zibby": "./bin/zibby.js"
|
|
7
|
+
"zibby": "./dist/bin/zibby.js"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
+
"build": "node ../scripts/build.mjs --extra-dirs bin",
|
|
10
11
|
"test": "vitest run test/auth*.test.js test/two-layer-auth.test.js",
|
|
11
12
|
"test:unit": "vitest run src/",
|
|
12
13
|
"test:auth": "vitest run test/auth*.test.js test/two-layer-auth.test.js",
|
|
@@ -31,25 +32,26 @@
|
|
|
31
32
|
"url": "https://github.com/ZibbyHQ/zibby-agent/issues"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@aws-sdk/client-sqs": "^3.
|
|
35
|
-
"@zibby/
|
|
36
|
-
"@zibby/
|
|
37
|
-
"@zibby/
|
|
35
|
+
"@aws-sdk/client-sqs": "^3.1029.0",
|
|
36
|
+
"@zibby/core": "^0.1.22",
|
|
37
|
+
"@zibby/memory": "^0.1.4",
|
|
38
|
+
"@zibby/skills": "^0.1.4",
|
|
39
|
+
"adm-zip": "^0.5.17",
|
|
38
40
|
"chalk": "^5.3.0",
|
|
39
41
|
"commander": "^12.0.0",
|
|
40
|
-
"dotenv": "^17.
|
|
42
|
+
"dotenv": "^17.4.1",
|
|
43
|
+
"express": "^4.18.2",
|
|
41
44
|
"glob": "^13.0.0",
|
|
42
|
-
"handlebars": "^4.7.
|
|
43
|
-
"inquirer": "^13.
|
|
45
|
+
"handlebars": "^4.7.9",
|
|
46
|
+
"inquirer": "^13.4.1",
|
|
47
|
+
"mem0ai": "^2.4.6",
|
|
44
48
|
"open": "^10.2.0",
|
|
45
|
-
"ora": "^8.0.1"
|
|
49
|
+
"ora": "^8.0.1",
|
|
50
|
+
"tar": "^7.5.2",
|
|
51
|
+
"ws": "^8.20.0"
|
|
46
52
|
},
|
|
47
53
|
"files": [
|
|
48
|
-
"
|
|
49
|
-
"src/",
|
|
50
|
-
"!src/**/__tests__/",
|
|
51
|
-
"!src/**/*.test.js",
|
|
52
|
-
"!src/**/*.spec.js",
|
|
54
|
+
"dist/",
|
|
53
55
|
"README.md",
|
|
54
56
|
"LICENSE"
|
|
55
57
|
],
|
|
@@ -57,6 +59,7 @@
|
|
|
57
59
|
"node": ">=18.0.0"
|
|
58
60
|
},
|
|
59
61
|
"devDependencies": {
|
|
60
|
-
"
|
|
62
|
+
"esbuild": "^0.28.0",
|
|
63
|
+
"vitest": "^4.1.4"
|
|
61
64
|
}
|
|
62
65
|
}
|