@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.
Files changed (46) hide show
  1. package/README.md +44 -44
  2. package/dist/auth/cli-login.js +18 -0
  3. package/dist/bin/zibby.js +2 -0
  4. package/dist/commands/agent-reliability.js +8 -0
  5. package/dist/commands/analyze-graph.js +18 -0
  6. package/dist/commands/chat-session-store.js +1 -0
  7. package/dist/commands/chat.js +79 -0
  8. package/dist/commands/ci-setup.js +18 -0
  9. package/dist/commands/generate.js +69 -0
  10. package/dist/commands/implement.js +65 -0
  11. package/dist/commands/init.js +344 -0
  12. package/dist/commands/list-projects.js +11 -0
  13. package/dist/commands/memory.js +39 -0
  14. package/dist/commands/run-capacity-queue-cli.js +4 -0
  15. package/dist/commands/run.js +112 -0
  16. package/dist/commands/setup-scripts.js +15 -0
  17. package/dist/commands/studio.js +33 -0
  18. package/dist/commands/upload.js +22 -0
  19. package/dist/commands/video.js +6 -0
  20. package/dist/commands/workflow.js +45 -0
  21. package/dist/config/config.js +1 -0
  22. package/dist/config/environments.js +1 -0
  23. package/dist/utils/chat-run-lifecycle.js +3 -0
  24. package/dist/utils/execution-context.js +1 -0
  25. package/dist/utils/progress-reporter.js +1 -0
  26. package/dist/utils/studio-cli-log-mirror.js +1 -0
  27. package/dist/utils/studio-installer.js +7 -0
  28. package/dist/utils/studio-launcher.js +1 -0
  29. package/package.json +19 -16
  30. package/bin/zibby.js +0 -273
  31. package/src/auth/cli-login.js +0 -404
  32. package/src/commands/analyze-graph.js +0 -336
  33. package/src/commands/ci-setup.js +0 -65
  34. package/src/commands/implement.js +0 -664
  35. package/src/commands/init.js +0 -770
  36. package/src/commands/list-projects.js +0 -77
  37. package/src/commands/memory.js +0 -171
  38. package/src/commands/run.js +0 -919
  39. package/src/commands/setup-scripts.js +0 -114
  40. package/src/commands/upload.js +0 -162
  41. package/src/commands/video.js +0 -30
  42. package/src/commands/workflow.js +0 -368
  43. package/src/config/config.js +0 -117
  44. package/src/config/environments.js +0 -145
  45. package/src/utils/execution-context.js +0 -25
  46. package/src/utils/progress-reporter.js +0 -155
@@ -0,0 +1,69 @@
1
+ import{invokeAgent as v,JIRA_TOOLS as T}from"@zibby/core";import{readFileSync as R,writeFileSync as x,existsSync as w,mkdirSync as A}from"fs";import{resolve as $,join as O}from"path";import{z as s}from"zod";import o from"chalk";const L=s.object({title:s.string(),testObjective:s.string(),url:s.string().optional(),testCredentials:s.array(s.object({field:s.string(),value:s.string()})).optional(),testSteps:s.array(s.string()),expectedResults:s.array(s.string()),priority:s.enum(["Critical","High","Medium","Low"]).optional(),category:s.string().optional()}),N=s.object({testCases:s.array(L),summary:s.object({totalTests:s.number(),coverageNotes:s.string().optional()}).optional()});function j(e,c){const t=[];if(t.push(`# ${e.title}`),c&&t.push(`# Ticket: ${c}`),e.priority&&t.push(`# Priority: ${e.priority}`),e.category&&t.push(`# Category: ${e.category}`),t.push(""),e.testObjective&&(t.push(`OBJECTIVE: ${e.testObjective}`),t.push("")),e.url&&(t.push(`START URL: ${e.url}`),t.push("")),e.testCredentials?.length>0){t.push("CREDENTIALS:");for(const i of e.testCredentials)t.push(` ${i.field}: ${i.value}`);t.push("")}if(t.push("STEPS:"),e.testSteps.forEach((i,a)=>{t.push(`${a+1}. ${i}`)}),t.push(""),e.expectedResults?.length>0){t.push("EXPECTED RESULTS:");for(const i of e.expectedResults)t.push(`- ${i}`)}return t.join(`
2
+ `).trim()}function P(e,c){return`You are an expert test engineer. Your job is to analyze a ticket and the local codebase, then generate detailed, actionable test case specifications.
3
+
4
+ WORKSPACE: ${c}
5
+
6
+ TICKET INFORMATION:
7
+ ${e}
8
+
9
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
10
+ STEP 1: EXPLORE THE CODEBASE (MANDATORY)
11
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
12
+
13
+ You MUST explore the codebase before generating tests. Use your tools to:
14
+
15
+ 1. Find and read routing files (App.tsx/jsx, routes.ts/js, pages/, app/)
16
+ 2. Understand the navigation hierarchy (auth \u2192 home \u2192 list \u2192 detail \u2192 sub-feature)
17
+ 3. Find authentication components and login flows
18
+ 4. Read page components referenced in routes
19
+ 5. Find related test files for patterns and conventions
20
+ 6. Check package.json for the framework and dependencies
21
+ 7. Look for .env.example or config files for URLs and settings
22
+
23
+ CRITICAL:
24
+ - Routes with :parameters (like /users/:id) require navigating to the parent list first
25
+ - Base ALL test steps on ACTUAL navigation paths from the codebase
26
+ - Use REAL URLs, component names, and navigation elements you find
27
+
28
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
29
+ STEP 2: GENERATE TEST CASES
30
+ \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
31
+
32
+ For each test case, provide:
33
+ - title: Clear test name
34
+ - testObjective: One sentence \u2014 what does this verify?
35
+ - url: Start URL (from codebase, e.g. http://localhost:3000)
36
+ - testCredentials: Login credentials if auth is needed
37
+ - testSteps: Plain English steps \u2014 full navigation from login to action
38
+ - expectedResults: What should happen after the steps
39
+ - priority: Critical / High / Medium / Low
40
+ - category: Smoke Test / Regression / Integration / Edge Case
41
+
42
+ Write 4-10 test cases covering:
43
+ - Happy path (the main flow the ticket describes)
44
+ - Edge cases (empty states, invalid input, boundary values)
45
+ - Error handling (what happens when things go wrong)
46
+ - Related features that might be affected
47
+
48
+ IMPORTANT: Write steps in natural language. Don't use CSS selectors or technical identifiers.
49
+ Example step: "Click the 'Submit' button" NOT "Click button[type=submit]"`}function S(e){return e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-|-$/g,"").slice(0,60)}async function D(e={}){const c=process.cwd(),t=e.agent||process.env.AGENT_TYPE,i=$(c,e.output||"test-specs");t||(console.error(o.red("\u274C No agent specified. Use --agent <codex|claude|cursor|gemini> or set AGENT_TYPE env")),process.exit(1));let a="",d=e.ticket||"unknown";if(e.input){const r=$(c,e.input);w(r)||(console.error(o.red(`\u274C Input file not found: ${r}`)),process.exit(1)),a=R(r,"utf-8")}else if(e.description)a=e.description;else if(e.ticket)try{const{getSkill:r}=await import("@zibby/core/framework/skill-registry.js"),l=r("jira");if(l?.handleToolCall){console.log(o.cyan(`\u{1F4CB} Fetching ticket ${e.ticket} from Jira...`));const n=JSON.parse(await l.handleToolCall(T.GET_ISSUE,{issueKey:e.ticket}));n.error&&(console.error(o.red(`\u274C Jira error: ${n.error}`)),process.exit(1)),d=n.key||e.ticket,a=`Key: ${n.key}
50
+ Summary: ${n.summary}
51
+ Type: ${n.type}
52
+ Priority: ${n.priority}
53
+ Status: ${n.status}
54
+ `,n.description&&(a+=`
55
+ Description:
56
+ ${n.description}
57
+ `),n.acceptanceCriteria&&(a+=`
58
+ Acceptance Criteria:
59
+ ${n.acceptanceCriteria}
60
+ `);const u=JSON.parse(await l.handleToolCall(T.GET_COMMENTS,{issueKey:e.ticket}));if(u.comments?.length>0){a+=`
61
+ Comments:
62
+ `;for(const g of u.comments)a+=` [${g.author}]: ${g.body}
63
+ `}console.log(o.green(`\u2705 Fetched: ${n.key} \u2014 ${n.summary}`))}else console.error(o.red("\u274C Jira skill not available. Use --input or --description instead.")),process.exit(1)}catch(r){console.error(o.red(`\u274C Failed to fetch ticket: ${r.message}`)),process.exit(1)}else console.error(o.red('\u274C Provide ticket info: --ticket SCRUM-123, --input file.txt, or --description "..."')),process.exit(1);const h=e.repo?$(c,e.repo):c,C=P(a,h);console.log(o.cyan(`
64
+ \u{1F9EA} Generating test specs with ${t} agent...`)),console.log(o.gray(` Workspace: ${h}`)),console.log(o.gray(` Output: ${i}`));const E={state:{agentType:t,workspace:h,config:{}}},k={workspace:h,schema:N,model:e.model||"gpt-4o-2024-08-06"};let f;try{f=await v(C,E,k)}catch(r){console.error(o.red(`
65
+ \u274C Agent failed: ${r.message}`)),process.exit(1)}const m=f?.structured||f,y=m?.testCases||[];y.length===0&&(console.error(o.yellow("\u26A0\uFE0F Agent returned no test cases")),process.exit(1)),A(i,{recursive:!0});const p=[];for(let r=0;r<y.length;r++){const l=y[r],n=S(l.title),u=`${S(d)}-${r+1}-${n}.txt`,g=O(i,u),b=j(l,d);x(g,b,"utf-8"),p.push({path:g,title:l.title,priority:l.priority}),console.log(o.green(` \u2705 ${u}`))}console.log(o.green.bold(`
66
+ \u2713 Generated ${p.length} test specs in ${i}`)),m?.summary?.coverageNotes&&console.log(o.gray(`
67
+ \u{1F4DD} ${m.summary.coverageNotes}`)),console.log(o.cyan(`
68
+ Next: run them with:`));for(const r of p)console.log(o.gray(` zibby run ${r.path}`));return console.log(o.cyan(`
69
+ Or from chat: the test runner will pick up these spec files automatically.`)),{files:p,ticketKey:d,total:p.length}}export{D as generateCommand};
@@ -0,0 +1,65 @@
1
+ import{existsSync as f,readFileSync as I}from"fs";import{join as o,dirname as b}from"path";import{fileURLToPath as x}from"url";import{invokeAgent as j}from"@zibby/core";import{fetchExecutionContext as D}from"../utils/execution-context.js";import{reportProgress as S,reportFinalStatus as O}from"../utils/progress-reporter.js";const L=x(import.meta.url),K=b(L);async function B(t){const{EXECUTION_ID:i,TICKET_KEY:l,PROJECT_ID:n,REPOS:e,_PRIMARY_REPO:s,_GITHUB_TOKEN:y,MODEL:k}=process.env;(!i||!l||!n)&&(console.error("\u274C Missing required environment variables:"),console.error(" EXECUTION_ID, TICKET_KEY, PROJECT_ID"),process.exit(1));const a=await D(i,n),g=a.ticketContext,m=e?JSON.parse(e):a.repos,R=m.find(_=>_.isPrimary)||m[0],h=process.cwd(),p={status:"running",steps:[]};try{await w("Start Environment",async()=>{}),await w("Clone Repositories",async()=>{const r=process.env.GITHUB_TOKEN,c=process.env.GITLAB_TOKEN||"",u=process.env.GITLAB_URL||"";for(const d of m){const T=o(h,d.name);let E=d.url;const P=d.provider==="gitlab"||u&&d.url.includes(new URL(u).host);if((d.provider==="github"||d.url.includes("github.com"))&&r)E=d.url.replace("https://github.com",`https://${r}@github.com`);else if(P&&c&&u)try{const v=new URL(u).host;E=d.url.replace(`https://${v}`,`https://oauth2:${c}@${v}`)}catch(v){console.warn(`\u26A0\uFE0F Failed to parse GITLAB_URL: ${v.message}`)}if(C(["git","clone",E,T],h),C(["git","checkout",d.branch],T),d.isPrimary){const v=`feature/${l.toLowerCase()}`;C(["git","checkout","-b",v],T)}}p.steps.push({name:"clone",status:"success",repoCount:m.length})});const _=await w("Load Ticket Context",async()=>(p.steps.push({name:"load_ticket",status:"success"}),g));await w("Install Dependencies",async()=>{for(const r of m){const c=o(h,r.name),u=U(c);try{C(u.installCommand,c)}catch{}}p.steps.push({name:"install_deps",status:"success"})});const A=await w("Detect Dev Command",async()=>{const r=o(h,R.name),c=["docker-compose.yml","docker-compose.yaml","compose.yml","compose.yaml"];for(const P of c)if(f(o(r,P)))return p.steps.push({name:"detect_dev",status:"success",command:"docker-compose up",type:"docker-compose"}),{command:"docker-compose up",type:"docker-compose",configFile:P};const u=o(r,"package.json");if(!f(u))return console.log(" \u26A0\uFE0F No package.json or docker-compose found"),p.steps.push({name:"detect_dev",status:"skipped"}),null;const T=JSON.parse(I(u,"utf-8")).scripts||{};let E=null;return T.dev?E="npm run dev":T.start?E="npm start":T["dev:local"]&&(E="npm run dev:local"),E?(p.steps.push({name:"detect_dev",status:"success",command:E,type:"npm"}),{command:E,type:"npm"}):(p.steps.push({name:"detect_dev",status:"skipped"}),null)});await w("Start Dev Server",async()=>{const r=o(h,R.name),c="docker-compose.test.yml";return f(o(r,c))?(C(["docker","compose","-f",c,"up","-d"],r),await new Promise(u=>setTimeout(u,1e4)),p.steps.push({name:"start_server",status:"success"}),!0):(console.log(` \u26A0\uFE0F No ${c} found, skipping server startup`),p.steps.push({name:"start_server",status:"skipped"}),null)}),await w("Run AI Agent Implementation",async()=>{const r=m.map(d=>{const T=o(h,d.name);return{...d,...U(T)}}),c=F(_,r,A),u=o(h,".cursor-prompt.md");require("fs").writeFileSync(u,c),await j(c,{state:{model:k,workspace:h}},{print:!0}),p.steps.push({name:"ai_agent",status:"success"})});const G=await w("Run E2E Tests",async()=>{const r=o(h,R.name);if(!f(o(r,"playwright.config.js"))&&!f(o(r,"playwright.config.ts")))return p.steps.push({name:"e2e_tests",status:"skipped"}),null;try{return C("npx playwright test --reporter=json",r),p.steps.push({name:"e2e_tests",status:"success"}),{passed:!0}}catch(c){throw C("docker compose -f docker-compose.test.yml down",r,{allowFailure:!0}),new Error(`E2E tests failed: ${c.message}`,{cause:c})}});try{C("docker compose -f docker-compose.test.yml down",o(h,R.name),{allowFailure:!0})}catch{}const $=await w("Create Pull Request",async()=>{const r=o(h,R.name),c=`feature/${l.toLowerCase()}`;return C(["git","add","."],r),C(["git","commit","-m",`feat(${l}): ${_.summary}`],r),C(["git","push","origin",c],r),console.log(" \u26A0\uFE0F PR creation via API removed (using SQS flow)"),p.steps.push({name:"create_pr",status:"skipped"}),null});await w("Report Results",async()=>{const r=o(h,R.name),c=o(r,"test-results"),u=[];f(c),p.status="completed",p.prUrl=$,p.videoUrls=u,await O(N(),{status:"completed",artifacts:{prUrl:$,videoUrls:u}})}),process.exit(0)}catch(_){console.error(""),console.error("\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"),console.error("\u2551 \u274C FAILED! \u2551"),console.error("\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"),console.error(""),console.error("Error:",_.message),console.error("Stack:",_.stack);try{await O(N(),{status:"failed",error:_.message})}catch(A){console.error("Failed to report error:",A.message)}process.exit(1)}}function N(){return{EXECUTION_ID:process.env.EXECUTION_ID,PROGRESS_API_URL:process.env.PROGRESS_API_URL,PROGRESS_QUEUE_URL:process.env.PROGRESS_QUEUE_URL,SQS_AUTH_TOKEN:process.env.SQS_AUTH_TOKEN,PROJECT_API_TOKEN:process.env.PROJECT_API_TOKEN}}async function w(t,i){const l=Date.now(),n=[];let e="";const s=console.log;console.log=(...a)=>{const g=a.join(" ");n.push(g),s(...a)};const y=N(),k=setInterval(()=>{const a=n.join(`
2
+ `);a!==e&&a.length>0&&(e=a,S(t,"running",a,y).catch(()=>{}))},2e3);try{await S(t,"running","",y);const a=await i(),g=`${((Date.now()-l)/1e3).toFixed(1)}s`;clearInterval(k),console.log=s;const m=n.join(`
3
+ `);return await S(t,"success",m||`Completed in ${g}`,y),a}catch(a){clearInterval(k),console.log=s;const g=n.join(`
4
+ `);throw await S(t,"failed",`${g}
5
+
6
+ Error: ${a.message}`,y),a}}function C(t,i,l={}){try{const{spawnSync:n}=require("child_process");let e;if(Array.isArray(t)){const[s,...y]=t;e=n(s,y,{cwd:i,encoding:"utf-8",stdio:["pipe","pipe","pipe"]})}else e=n(t,{cwd:i,shell:!0,encoding:"utf-8",stdio:["pipe","pipe","pipe"]});if(e.stdout&&console.log(e.stdout),e.stderr&&console.log(e.stderr),e.status!==0&&!l.allowFailure){const s=Array.isArray(t)?t.join(" "):t;throw new Error(`Command failed with exit code ${e.status}: ${s}`)}return e.stdout||e.stderr}catch(n){if(l.allowFailure)return null;throw n}}function U(t){const i=o(t,".zibby.yml");if(f(i))try{const e=require("js-yaml").load(I(i,"utf-8"));return{name:e.name||"Custom Project",framework:e.framework||"Custom",language:e.language||"Custom",testCommand:e.test||"make test",installCommand:e.install||"make install",custom:!0}}catch{console.warn("Invalid .zibby.yml, falling back to auto-detection")}const l=o(t,"package.json");if(f(l)){const n=JSON.parse(I(l,"utf-8")),e={...n.dependencies,...n.devDependencies};let s="Node.js";return e.next?s="Next.js":e["react-scripts"]?s="Create React App":e.vite&&e.react?s="React + Vite":e["@angular/core"]?s="Angular":e.vue?s="Vue.js":e.express&&(s="Express.js"),{name:n.name||"Unknown Project",framework:s,language:"JavaScript/TypeScript",testCommand:n.scripts?.test||"npm test",installCommand:"npm install"}}return f(o(t,"requirements.txt"))||f(o(t,"pyproject.toml"))?{name:"Python Project",framework:f(o(t,"manage.py"))?"Django":f(o(t,"app.py"))?"Flask":"Python",language:"Python",testCommand:"pytest",installCommand:"pip install -r requirements.txt"}:f(o(t,"Gemfile"))?{name:"Ruby Project",framework:"Rails",language:"Ruby",testCommand:"bundle exec rspec",installCommand:"bundle install"}:f(o(t,"go.mod"))?{name:"Go Project",framework:"Go",language:"Go",testCommand:"go test ./...",installCommand:"go mod download"}:f(o(t,"pom.xml"))?{name:"Java Project",framework:"Spring Boot",language:"Java",testCommand:"./mvnw test",installCommand:"./mvnw install"}:{name:"Unknown Project",framework:"Unknown",language:"Unknown",testCommand:"make test",installCommand:"make install"}}function F(t,i,l){const n=o(K,"../../prompts/implement-ticket.md");let e;try{e=I(n,"utf-8")}catch{e=`
7
+ # Implement Ticket: {{TICKET_KEY}}
8
+
9
+ ## Project Context
10
+ {{PROJECT_CONTEXT}}
11
+
12
+ ## Ticket Summary
13
+ {{TICKET_SUMMARY}}
14
+
15
+ ## Description
16
+ {{TICKET_DESCRIPTION}}
17
+
18
+ ## Acceptance Criteria
19
+ {{ACCEPTANCE_CRITERIA}}
20
+
21
+ {{#if ADDITIONAL_CONTEXT}}
22
+ ## Additional Context from User
23
+ {{ADDITIONAL_CONTEXT}}
24
+
25
+ {{/if}}
26
+ ---
27
+
28
+ You are implementing this ticket. Follow these steps:
29
+
30
+ 1. Read the existing codebase and understand the patterns
31
+ 2. Implement the feature as described
32
+ 3. Write tests to verify the functionality
33
+ 4. Ensure all tests pass
34
+ 5. Fix any linter errors
35
+
36
+ Now implement this ticket completely!
37
+ `.trim()}const s=i.find(g=>g.isPrimary)||i[0];let y;l?.type==="docker-compose"?y=`\`docker-compose up\` (using ${l.configFile})`:l?.command?y=`\`cd ${s.name} && ${l.command}\``:y="`npm run dev` (or check package.json scripts)";let k;if(i.length===1)k=`
38
+ You are working in **${s.name}**, a ${s.framework} project.
39
+
40
+ **Commands:**
41
+ - Dev server: ${y}
42
+ - Run tests: \`cd ${s.name} && ${s.testCommand}\`
43
+
44
+ You have full access to the codebase in the current directory.
45
+ `.trim();else{const g=i.map(m=>`- **${m.name}/** (${m.framework})${m.isPrimary?" \u2190 **MAKE CHANGES HERE**":" (reference only)"}`).join(`
46
+ `);k=`
47
+ You are working in a **multi-repository** setup with ${i.length} repositories:
48
+
49
+ ${g}
50
+
51
+ **Primary Repository:** ${s.name}
52
+ - This is where you should implement the feature
53
+ - Framework: ${s.framework}
54
+ - Dev server: ${y}
55
+ - Run tests: \`cd ${s.name} && ${s.testCommand}\`
56
+
57
+ **Other Repositories:**
58
+ ${i.filter(m=>!m.isPrimary).map(m=>`- **${m.name}**: You can read code from here for reference (shared libraries, services, etc.)`).join(`
59
+ `)||"(none)"}
60
+
61
+ **Important:** Make all code changes in the \`${s.name}/\` directory only.
62
+ `.trim()}let a=e.replace(/\{\{TICKET_KEY\}\}/g,t.ticketKey||t.key||"UNKNOWN").replace(/\{\{PROJECT_CONTEXT\}\}/g,k).replace(/\{\{TICKET_SUMMARY\}\}/g,t.summary||"No summary").replace(/\{\{TICKET_DESCRIPTION\}\}/g,t.description||"No description provided").replace(/\{\{ACCEPTANCE_CRITERIA\}\}/g,t.acceptanceCriteria||"Not specified");if(t.additionalContext){const g=`## Additional Context from User
63
+ ${t.additionalContext}
64
+
65
+ `;a=a.replace(/\{\{#if ADDITIONAL_CONTEXT\}\}[\s\S]*?\{\{\/if\}\}/g,g)}else a=a.replace(/\{\{#if ADDITIONAL_CONTEXT\}\}[\s\S]*?\{\{\/if\}\}/g,"");return a}export{B as implementCommand};
@@ -0,0 +1,344 @@
1
+ import{mkdir as A,writeFile as g,readFile as w}from"fs/promises";import{existsSync as h,readdirSync as j}from"fs";import{join as i,resolve as Y,dirname as N}from"path";import{homedir as I}from"os";import K from"inquirer";import e from"chalk";import P from"ora";import{spawn as T,execSync as O}from"child_process";import{fileURLToPath as Z}from"url";const G=Z(import.meta.url),H=N(G);async function F(){try{const r=process.platform==="win32",t=O("npm config get prefix",{encoding:"utf-8"}).trim(),u=r?t:`${t}/bin`,d=u.replace(/^~/,I()),s=r?";":":";if(process.env.PATH.split(s).some(v=>{const M=v.replace(/^~/,I());return M===d||M===u}))return;console.log(e.yellow("\u26A0\uFE0F npm global bin not in PATH")),console.log(e.gray(` Location: ${d}`)),console.log();const{shouldAddPath:n}=await K.prompt([{type:"confirm",name:"shouldAddPath",message:"Add npm global bin to your shell PATH automatically?",default:!0}]);if(!n){r?(console.log(e.gray(`
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
+ `))):(console.log(e.gray(`
4
+ \u{1F4A1} To add manually, run:`)),console.log(e.gray(` echo 'export PATH="${d}:$PATH"' >> ~/.zshrc`)),console.log(e.gray(` source ~/.zshrc
5
+ `)));return}if(r){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=i(I(),".zshrc");else if(x.includes("bash"))p=h(i(I(),".bashrc"))?i(I(),".bashrc"):i(I(),".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(h(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
+ `));return}}const b=`
8
+ # npm global bin (added by zibby)
9
+ export PATH="${d}:$PATH"
10
+ `;await g(p,(h(p)?await w(p,"utf-8"):"")+b),console.log(e.green(`\u2705 Added to ${p}`)),console.log(e.yellow(`
11
+ \u{1F4A1} Run this to activate in current session:`)),console.log(e.gray(` source ${p}
12
+ `))}catch{}}async function me(r,t){console.log(e.bold.cyan(`
13
+ \u{1F3AD} Welcome to Zibby Test Automation!
14
+ `));const u=!t.skipMemory,d=["dolt","mem0"].includes(String(t.memoryBackend||"").toLowerCase())?String(t.memoryBackend).toLowerCase():"dolt";await F();const s=r?Y(process.cwd(),r):process.cwd(),E=r||"zibby-tests",y=!!r;y&&h(s)&&(console.log(e.red(`
15
+ \u274C Directory "${r}" already exists!
16
+ `)),process.exit(1)),!y&&h(i(s,".zibby.config.mjs"))&&!t.force&&(console.log(e.yellow(`
17
+ \u26A0\uFE0F Zibby is already initialized in this directory!
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)),t.force&&!y&&console.log(e.cyan(`
20
+ Reinitializing Zibby configuration...
21
+ `));let n;if(t.agent&&(t.headed||t.headless))console.log(e.cyan(`Setting up with provided options...
22
+ `)),n={agent:t.agent,browserMode:t.headless?"headless":"headed",apiKey:t.apiKey||null,cloudSync:!!(t.cloudSync||t.apiKey)};else{const b=[];t.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"}),!t.headed&&!t.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"}),t.apiKey||b.push({type:"input",name:"apiKey",message:"Enable cloud sync? Enter project ZIBBY_API_KEY (or press Enter to skip):"}),n=b.length>0?await K.prompt(b):{},n.agent=t.agent||n.agent,n.browserMode=t.headless?"headless":t.headed?"headed":n.browserMode,n.apiKey=t.apiKey||n.apiKey,n.cloudSync=!!(t.cloudSync||t.apiKey||n.apiKey&&n.apiKey.trim())}n.mcp="playwright",n.setupMcp=n.agent==="cursor";const p=P("Setting up Zibby...").start();try{if(y&&await A(s,{recursive:!0}),await A(i(s,"test-specs/examples"),{recursive:!0}),await A(i(s,"tests"),{recursive:!0}),await A(i(s,".zibby/output"),{recursive:!0}),await A(i(s,".zibby/commands"),{recursive:!0}),u&&d==="dolt")try{const{initMemory:o,DoltDB:l}=await import("@zibby/memory");if(l.isAvailable()){const{created:a}=o(s);a&&(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=t.template||"browser-test-automation";try{const{graphPath:o,nodesPath:l,readmePath:a,resultHandlerPath:m,template:c}=b.getTemplateFiles(v),f=i(s,".zibby"),z=await w(o,"utf-8");if(await g(i(f,"graph.mjs"),z),m){const C=await w(m,"utf-8");await g(i(f,"result-handler.mjs"),C)}const B=await w(a,"utf-8");await g(i(f,"README.md"),B),await A(i(f,"nodes"),{recursive:!0});const{readdirSync:k}=await import("fs"),S=k(l);for(const C of S){let D=await w(i(l,C),"utf-8");!u&&C==="execute-live.mjs"&&(D=D.replace("skills: [SKILLS.BROWSER, SKILLS.MEMORY],","skills: [SKILLS.BROWSER],")),await g(i(f,"nodes",C),D)}const $=i(c.path,"chat.mjs");if(h($)){const C=await w($,"utf-8");await g(i(f,"chat.mjs"),C)}}catch(o){throw p.fail(`Failed to scaffold template: ${o.message}`),o}p.text="Generating configuration files...";const M=W(n,t,{memoryBackend:d});await g(i(s,".zibby.config.mjs"),M);const L=n.apiKey&&n.apiKey.trim()?V(n,n.apiKey.trim(),d):U(n,d);if(await g(i(s,".env.example"),L),y){const o=q(E,n,{memoryBackend:d});await g(i(s,"package.json"),o)}if(!h(i(s,".gitignore"))){const o=X();await g(i(s,".gitignore"),o)}if(!h(i(s,"playwright.config.js"))){const o=J("on");await g(i(s,"playwright.config.js"),o)}if(!h(i(s,"test-specs/examples/example-domain.txt"))){const o=Q();await g(i(s,"test-specs/examples/example-domain.txt"),o)}const R=Y(H,"../../../../examples/.zibby/commands");if(h(R)){const o=j(R).filter(l=>l.toLowerCase().endsWith(".md"));for(const l of o){const a=i(s,".zibby/commands",l);if(t.force||!h(a)){const m=await w(i(R,l),"utf-8");await g(a,m)}}}if(!h(i(s,".zibby/commands/example.md"))){const o=ee();await g(i(s,".zibby/commands/example.md"),o)}if(y){const o=te(E,n);await g(i(s,"README.md"),o)}if(p.succeed(y?"Project created!":"Zibby initialized!"),y&&!t.skipInstall){const o=P("Installing dependencies...").start();await new Promise((l,a)=>{T("npm",["install"],{cwd:s,stdio:"pipe"}).on("close",c=>{c===0?(o.succeed("Dependencies installed!"),l()):(o.fail("Failed to install dependencies"),a(new Error("npm install failed")))})})}else y||console.log(e.gray(`
23
+ Make sure @zibby/cli is installed in your package.json
24
+ `));if(!y&&u&&d==="mem0"&&(console.log(e.yellow(`
25
+ 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
26
+ `))),!t.skipInstall){const o=P("Installing Playwright browsers...").start();await new Promise(l=>{const a=T("npx",["playwright","install","chromium"],{cwd:s,stdio:"pipe"});let m="";a.stdout.on("data",c=>{m+=c.toString()}),a.stderr.on("data",c=>{m+=c.toString()}),a.on("close",c=>{c===0?(m.includes("already installed")||m.includes("up to date")?o.succeed("Playwright browsers already installed"):o.succeed("Playwright browsers installed!"),l()):(o.warn("Could not verify Playwright browsers"),console.log(e.yellow(`
27
+ \u26A0\uFE0F If tests fail, run: npx playwright install
28
+ `)),l())})})}if(n.agent==="codex"&&!t.skipInstall){const o=P("Checking Codex CLI...").start();try{O("codex --version",{encoding:"utf-8",timeout:5e3,stdio:"pipe"}),o.succeed("Codex CLI already installed")}catch{o.text="Installing Codex CLI...";try{await new Promise((l,a)=>{T("npm",["install","-g","@openai/codex"],{stdio:"pipe"}).on("close",c=>{c===0?(o.succeed("Codex CLI installed!"),l()):a(new Error("npm install -g @openai/codex failed"))})})}catch{o.fail("Could not install Codex CLI"),console.log(e.yellow(` Install manually: npm install -g @openai/codex
29
+ `))}}}if(n.agent==="gemini"&&!t.skipInstall){const o=P("Checking Gemini CLI...").start();try{O("gemini --version",{encoding:"utf-8",timeout:5e3,stdio:"pipe"}),o.succeed("Gemini CLI already installed")}catch{o.text="Installing Gemini CLI...";try{await new Promise((a,m)=>{T("npm",["install","-g","@google/gemini-cli"],{stdio:"pipe"}).on("close",f=>{f===0?(o.succeed("Gemini CLI installed!"),a()):m(new Error("npm install -g @google/gemini-cli failed"))})})}catch{o.fail("Could not install Gemini CLI"),console.log(e.yellow(` Install manually: npm install -g @google/gemini-cli
30
+ `))}}const l=P("Configuring Gemini MCP servers...").start();try{const a=i(I(),".gemini"),m=i(a,"settings.json");h(a)||await A(a,{recursive:!0});let c={mcpServers:{}};if(h(m))try{const S=await w(m,"utf-8");c=JSON.parse(S),c.mcpServers||(c.mcpServers={})}catch{}let f;try{const{createRequire:S}=await import("module");f=S(import.meta.url).resolve("@zibby/mcp-browser/bin/mcp-browser-zibby.js")}catch{f="@playwright/mcp"}const z=n.browserMode!=="headless",B=f==="@playwright/mcp"?["-y","@playwright/mcp","--isolated","--save-video=1280x720","--viewport-size=1280x720","--output-dir","test-results"]:[f,"--isolated","--save-video=1280x720","--viewport-size=1280x720","--output-dir=test-results"];z||B.push("--headless"),c.mcpServers["playwright-official"]={command:f==="@playwright/mcp"?"npx":"node",args:B},await g(m,`${JSON.stringify(c,null,2)}
31
+ `,"utf-8");let k="Gemini MCP configured";z?k+=" (headed mode - visible browser)":k+=" (headless mode - hidden browser)",l.succeed(k)}catch(a){l.fail("MCP setup failed"),console.log(e.yellow(" You may need to configure ~/.gemini/settings.json manually")),console.log(e.gray(` Error: ${a.message}
32
+ `))}}if(n.agent==="cursor"&&n.setupMcp){const o=P("Setting up Playwright MCP...").start();try{const{setupPlaywrightMcpCommand:l}=await import("./setup-scripts.js"),a=n.cloudSync||!1,m=n.browserMode!=="headless";await l({headed:m,cloudSync:a,video:"on",viewport:{width:1280,height:720}});let c="Playwright MCP configured";m?c+=" (headed mode - visible browser)":c+=" (headless mode - hidden browser)",a&&(c+=" + Zibby MCP (cloud sync)"),o.succeed(c),a&&console.log(e.gray(`
33
+ Copy .env.example to .env and set ZIBBY_API_KEY to enable uploads
34
+ `))}catch(l){o.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
35
+ `)),console.log(e.gray(` Error: ${l.message}
36
+ `))}}console.log(e.bold.green(`
37
+ \u{1F389} All set!
38
+ `)),console.log(e.cyan("Start the Zibby Chat Agent:")),y&&console.log(e.white(` cd ${r}`)),console.log(e.white(` zibby
39
+ `)),console.log(e.cyan("Or run a test directly:")),console.log(e.white(` zibby run test-specs/examples/example-domain.txt
40
+ `)),console.log(e.cyan("Next steps:"));let _=1;console.log(e.white(` ${_++}. cp .env.example .env ${e.gray("# then add your API keys")}`)),u&&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>
41
+ `))}catch(b){p.fail("Failed to create project"),console.error(e.red(`
42
+ \u274C Error: ${b.message}
43
+ `)),process.exit(1)}}function W(r,t={},u={}){const d=["dolt","mem0"].includes(String(u.memoryBackend||"").toLowerCase())?String(u.memoryBackend).toLowerCase():"dolt",s={claude:`
44
+ claude: {
45
+ model: 'auto', // Options: 'auto', 'sonnet-4.6', 'opus-4.6', 'sonnet-4.5', 'opus-4.5'
46
+ maxTokens: 4096,
47
+ },`,cursor:`
48
+ cursor: {
49
+ model: 'auto', // Options: 'auto', 'opus-4.5', 'opus-4.5-thinking', 'sonnet-4.5', 'sonnet-4.5-thinking', 'composer-1', 'gpt-5.2-codex', 'gpt-5.2', 'gpt-5.3', 'gpt-5.4', 'gemini-3-pro', 'gemini-3-flash'
50
+ },`,codex:`
51
+ codex: {
52
+ model: 'gpt-5.2-codex', // Options: 'auto', 'o4-mini', 'o3', 'codex-mini', 'gpt-4o', 'gpt-5.2-codex', 'gpt-5.2', 'gpt-5.3', 'gpt-5.4'
53
+ },`,gemini:`
54
+ gemini: {
55
+ model: 'gemini-2.5-pro', // Options: 'auto', 'gemini-2.5-pro', 'gemini-2.5-flash'
56
+ },`},E=r.agent,y=Object.entries(s).filter(([n])=>n!==E).map(([,n])=>n.split(`
57
+ `).map(x=>x.trim()?` // ${x.trimStart()}`:x).join(`
58
+ `)).join(`
59
+ `);return`export default {
60
+ // AI agent settings
61
+ agent: {${s[E]}
62
+ ${y}
63
+ strictMode: false,
64
+ },
65
+
66
+ // Browser for Zibby MCP (execute_live). Wizard headed/headless is stored here for \`zibby run\` logs
67
+ // and workflow config. Runtime strategies attach MCP per run (no global Gemini settings mutation).
68
+ browser: {
69
+ mcp: 'playwright',
70
+ headless: ${r.browserMode==="headless"},
71
+ },
72
+
73
+ // Chat memory backend adapter (dolt | mem0)
74
+ memory: {
75
+ backend: '${d}',
76
+ },
77
+
78
+ // Advanced: Override models per node (optional)
79
+ // models: {
80
+ // default: 'auto', // Fallback for all nodes
81
+ // execute_live: 'claude-opus-4', // Override specific node
82
+ // },
83
+
84
+ // Folder paths
85
+ paths: {
86
+ specs: 'test-specs', // Where your .txt test specs are
87
+ generated: 'tests', // Where generated .spec.js files go
88
+ output: '.zibby/output', // Where workflow execution results are saved (default: .zibby/output)
89
+ // sessionPrefix: 'run', // Optional: prefix for session folders (e.g., run_1772788458045)
90
+ },
91
+
92
+ // Context discovery - auto-discovers CONTEXT.md & AGENTS.md files (cascades from root \u2192 spec directory)
93
+ // Override filenames to search for different files
94
+ context: {
95
+ filenames: ['CONTEXT.md', 'AGENTS.md'], // Auto-discover these files (walks up from spec directory)
96
+ discovery: {
97
+ env: \`env-\${process.env.ENV || 'local'}.js\`, // Additional explicit files
98
+ // fixtures: 'fixtures.js', // Add more as needed
99
+ }
100
+ },
101
+
102
+ // Video recording (affects playwright.config.js generation)
103
+ video: 'on', // Options: 'off', 'on', 'retain-on-failure', 'on-first-retry'
104
+
105
+ // Browser viewport size for test execution
106
+ // Default: 1280x720 (works well on most screens)
107
+ // For larger displays: { width: 1920, height: 1080 }
108
+ // For mobile testing: { width: 375, height: 667 } (iPhone SE)
109
+ viewport: { width: 1280, height: 720 },
110
+
111
+ // Playwright artifacts (screenshots, traces, videos)
112
+ // Traces contain EXACT selectors for 100% accurate script generation
113
+ // Default: true (stored in test-results/playwright, separate from workflow output)
114
+ playwrightArtifacts: true,
115
+
116
+ // Parallel runs: max concurrent \`zibby run\` processes from the chat agent (run_test) and
117
+ // the number of lanes on Studio Mission Control (when it can read this project\u2019s config).
118
+ parallel: {
119
+ maxConcurrentRuns: 8,
120
+ },
121
+
122
+ // Cloud sync - auto-upload test results & videos (requires ZIBBY_API_KEY in .env)
123
+ cloudSync: ${r.cloudSync||!1}
124
+ };
125
+ `}function U(r,t="dolt"){return`# Zibby Test Automation - Environment Variables
126
+
127
+ # AI Provider Keys
128
+ ${{claude:"ANTHROPIC_API_KEY=sk-ant-your_key_here",cursor:`# Cursor Agent uses cursor-agent CLI \u2014 no API key needed
129
+ # Install: curl https://cursor.com/install -fsS | bash`,codex:"OPENAI_API_KEY=sk-your_key_here",gemini:"GEMINI_API_KEY=your_key_here"}[r.agent]||""}
130
+
131
+ # Zibby Cloud Sync (for uploading test results & videos)
132
+ # Get your API key from: https://zibby.app/settings/tokens
133
+ # ZIBBY_API_KEY=zby_your_api_key_here
134
+
135
+ # Test Memory (Dolt DB) - Auto-compaction settings
136
+ # ZIBBY_MEMORY_MAX_RUNS=3000 # Max test runs to keep per spec
137
+ # ZIBBY_MEMORY_MAX_AGE=1095 # Max age in days for stale data (~3 years)
138
+ # ZIBBY_MEMORY_COMPACT_EVERY=1500 # Auto-compact every N runs (0 to disable)
139
+
140
+ # Chat memory backend
141
+ ZIBBY_MEMORY_BACKEND=${t}
142
+ `}function V(r,t,u="dolt"){return`# Zibby Test Automation - Environment Variables
143
+
144
+ # AI Provider Keys
145
+ ${{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"}[r.agent]||""}
146
+
147
+ # Zibby Cloud Sync
148
+ ZIBBY_API_KEY=${t}
149
+
150
+ # Test Memory (Dolt DB) - Auto-compaction settings
151
+ # ZIBBY_MEMORY_MAX_RUNS=3000 # Max test runs to keep per spec
152
+ # ZIBBY_MEMORY_MAX_AGE=1095 # Max age in days for stale data (~3 years)
153
+ # ZIBBY_MEMORY_COMPACT_EVERY=1500 # Auto-compact every N runs (0 to disable)
154
+
155
+ # Chat memory backend
156
+ ZIBBY_MEMORY_BACKEND=${u}
157
+ `}function q(r,t,u={}){const d={"@zibby/cli":"^0.1.25","@zibby/core":"^0.1.20"};return u.memoryBackend==="mem0"&&(d.mem0ai="^2.4.6"),JSON.stringify({name:r,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 X(){return`# Dependencies
158
+ node_modules/
159
+
160
+ # Test artifacts
161
+ .zibby/output/
162
+ playwright-report/
163
+ tests/
164
+ *.webm
165
+ *.mp4
166
+
167
+ # Environment variables
168
+ .env
169
+
170
+ # OS
171
+ .DS_Store
172
+ Thumbs.db
173
+
174
+ # IDE
175
+ .vscode/
176
+ .idea/
177
+
178
+ # Zibby cache
179
+ .zibby/cache/
180
+ .zibby/tenancy.json
181
+
182
+ # Zibby memory (Dolt DB synced separately via dolt remote, not git)
183
+ .zibby/memory/
184
+ .zibby/memory-context.md
185
+ `}function J(r="off",t=null){return`import { defineConfig} from '@playwright/test';
186
+
187
+ export default defineConfig({
188
+ testDir: './tests',
189
+ ${t?` outputDir: '${t}',
190
+ `:` outputDir: 'test-results/playwright', // Keep Playwright artifacts separate from Zibby workflow output
191
+ `} timeout: 30000,
192
+ retries: 0,
193
+ workers: 1,
194
+
195
+ use: {
196
+ headless: process.env.PLAYWRIGHT_HEADLESS === '1' ? true : false,
197
+ viewport: { width: 1280, height: 720 },
198
+ screenshot: 'off',
199
+ video: 'off',
200
+ },
201
+
202
+ reporter: [
203
+ ['html'],
204
+ ['list']
205
+ ],
206
+ });
207
+ `}function Q(){return`Test Specification: Example Domain Navigation
208
+ ==============================================
209
+
210
+ Application: Example Domain
211
+ URL: https://example.com
212
+ Feature: Basic navigation and link verification
213
+
214
+ Test Objective:
215
+ ---------------
216
+ Verify that the example.com website loads correctly, displays expected content,
217
+ and navigational links function as intended.
218
+
219
+ Test Steps:
220
+ -----------
221
+ 1. Navigate to https://example.com
222
+ 2. Verify the page title contains "Example Domain"
223
+ 3. Verify the main heading is visible and contains "Example Domain"
224
+ 4. Locate and verify the "More information..." link is present
225
+ 5. Click the "More information..." link
226
+ 6. Verify navigation to IANA domain information page
227
+ 7. Verify the new page URL contains "iana.org"
228
+ 8. Verify the page loaded successfully (no errors)
229
+
230
+ Expected Results:
231
+ -----------------
232
+ - Initial page loads within 3 seconds
233
+ - "Example Domain" heading is clearly visible
234
+ - "More information..." link is clickable and visible
235
+ - Navigation to IANA page succeeds
236
+ - No console errors or network failures
237
+ - Page title updates after navigation
238
+
239
+ Notes:
240
+ ------
241
+ - Uses a stable, public domain (example.com) maintained by IANA
242
+ - No authentication required
243
+ - Suitable for CI/CD smoke testing
244
+ `}function ee(){return`# Example command template
245
+
246
+ Use this command when the user asks for a concise testing task breakdown.
247
+
248
+ Required output format:
249
+ 1. Goal
250
+ 2. Risks
251
+ 3. Steps
252
+ 4. Expected result
253
+
254
+ Keep the response actionable and short.
255
+ `}function te(r,t){return`# ${r}
256
+
257
+ AI-powered test automation with Zibby.
258
+
259
+ ## Setup
260
+
261
+ 1. Install dependencies:
262
+ \`\`\`bash
263
+ npm install
264
+ \`\`\`
265
+
266
+ 2. Configure environment:
267
+ \`\`\`bash
268
+ cp .env.example .env
269
+ # Edit .env and add your ${{claude:"ANTHROPIC_API_KEY",codex:"OPENAI_API_KEY",gemini:"GEMINI_API_KEY",cursor:"CURSOR_API_KEY (optional)"}[t.agent]}
270
+ \`\`\`
271
+
272
+ 3. Run example test:
273
+ \`\`\`bash
274
+ npx zibby run test-specs/examples/example-domain.txt
275
+ \`\`\`
276
+
277
+ ## Usage
278
+
279
+ ### Run Test Specs
280
+
281
+ \`\`\`bash
282
+ # Run a test spec (executes + generates + verifies)
283
+ npx zibby run test-specs/auth/login.txt
284
+
285
+ # Run in headless mode
286
+ npx zibby run test-specs/auth/login.txt --headless
287
+ \`\`\`
288
+
289
+ ### Write Test Specs
290
+
291
+ Create test specifications in \`test-specs/\`:
292
+
293
+ \`\`\`
294
+ test-specs/
295
+ auth/
296
+ login.txt
297
+ logout.txt
298
+ dashboard/
299
+ overview.txt
300
+ \`\`\`
301
+
302
+ ### Run Generated Tests
303
+
304
+ \`\`\`bash
305
+ # Run all generated tests
306
+ npx playwright test
307
+
308
+ # Run specific test
309
+ npx playwright test tests/auth/login.spec.js
310
+
311
+ # Run with UI
312
+ npx playwright test --ui
313
+ \`\`\`
314
+
315
+ ## Configuration
316
+
317
+ Edit \`.zibby.config.mjs\` to customize:
318
+ - Agent settings (model, temperature)
319
+ - Browser settings (headless, viewport)
320
+ - Cloud sync${t.cloudSync?" (enabled)":" (disabled)"}
321
+ - Self-healing behavior
322
+
323
+ ## Project Structure
324
+
325
+ \`\`\`
326
+ ${r}/
327
+ \u251C\u2500\u2500 .zibby/
328
+ \u2502 \u251C\u2500\u2500 graph.mjs # Workflow definition
329
+ \u2502 \u251C\u2500\u2500 nodes/ # Custom nodes
330
+ \u2502 \u2514\u2500\u2500 output/ # Workflow execution results (gitignored)
331
+ \u2502 \u2514\u2500\u2500 sessions/ # Session artifacts & recordings
332
+ \u251C\u2500\u2500 .zibby.config.mjs # Configuration
333
+ \u251C\u2500\u2500 .env.example # Environment template (cp to .env and configure)
334
+ \u251C\u2500\u2500 test-specs/ # Test specifications (committed)
335
+ \u2502 \u2514\u2500\u2500 examples/
336
+ \u2502 \u2514\u2500\u2500 example-domain.txt
337
+ \u2514\u2500\u2500 tests/ # Generated tests (gitignored)
338
+ \`\`\`
339
+
340
+ ## Learn More
341
+
342
+ - Documentation: https://docs.zibby.dev
343
+ - Examples: https://github.com/zibby/examples
344
+ `}export{me as initCommand};
@@ -0,0 +1,11 @@
1
+ import t from"chalk";import k from"ora";import{getApiUrl as u}from"../config/environments.js";import{getSessionToken as y,getUserInfo as I,saveProjects as E}from"../config/config.js";async function z(){const s=k("Fetching projects...").start();try{const n=y(),r=I();n||(s.fail("Not logged in"),console.log(t.yellow(`
2
+ Please log in first:`)),console.log(t.gray(` zibby login
3
+ `)),process.exit(1)),r&&(console.log(t.gray(`Logged in as: ${r.email}`)),r.account_id&&console.log(t.gray(`Account ID: ${r.account_id}
4
+ `)));const p=u(),c=await fetch(`${p}/projects`,{headers:{Authorization:`Bearer ${n}`}});if(!c.ok){const o=await c.json().catch(()=>({}));throw new Error(o.error||"Failed to fetch projects")}const e=(await c.json()).projects||[],g=e.map(o=>({name:o.name,projectId:o.projectId,apiToken:o.apiToken}));if(E(g),s.stop(),e.length===0){console.log(`
5
+ No projects found`),console.log(`Create a project at: https://zibby.app/projects
6
+ `);return}const d=o=>o&&o.length>8?`${o.substring(0,8)}\u2022\u2022\u2022\u2022`:o||"-",a=Math.max(4,...e.map(o=>o.name.length)),i=36,l=14,j=` ${"Name".padEnd(a)} ${"Project ID".padEnd(i)} ${"API Token".padEnd(l)}`,m=` ${"\u2500".repeat(a)} ${"\u2500".repeat(i)} ${"\u2500".repeat(l)}`;console.log(`
7
+ Your Projects (${e.length})
8
+ `),console.log(j),console.log(m);for(const o of e){const f=o.name.padEnd(a),h=o.projectId.padEnd(i),$=d(o.apiToken).padEnd(l);console.log(` ${f} ${h} ${$}`)}console.log(""),console.log("\u{1F4A1} Use the Project ID with --project flag:"),console.log(` zibby run test-specs/login.txt --project <project-id> --sync
9
+ `)}catch(n){s.fail("Failed to fetch projects"),console.error(t.red(`
10
+ \u274C Error: ${n.message}
11
+ `)),process.exit(1)}}export{z as listProjectsCommand};
@@ -0,0 +1,39 @@
1
+ import o from"chalk";const y=`
2
+ Install @zibby/memory and Dolt to enable test memory:
3
+
4
+ npm install @zibby/memory # add the package
5
+ brew install dolt # macOS
6
+ # or: curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash
7
+ `;async function d(){try{return await import("@zibby/memory")}catch{return console.log(o.yellow(`
8
+ @zibby/memory is not installed.
9
+ `)),console.log(o.white(y)),null}}async function h(){const s=await d();if(!s)return;const l=process.cwd(),e=s.getStats(l);if(!e.available){console.log(o.red(`
10
+ Dolt is not installed.
11
+ `)),console.log(o.white(" Install Dolt to enable test memory:")),console.log(o.gray(" brew install dolt # macOS")),console.log(o.gray(` curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash # Linux
12
+ `));return}if(!e.initialized){console.log(o.yellow(`
13
+ Memory database not initialized.
14
+ `)),console.log(o.white(" Run `zibby init` or `zibby memory init` to set it up.\n"));return}console.log(o.bold.cyan(`
15
+ Zibby Test Memory
16
+ `)),console.log(o.gray(` Dolt: ${e.doltVersion}`));const a=o.gray(` ${"\u2500".repeat(40)}`);console.log(a);const n=e.counts;if(console.log(o.white(` Test runs: ${o.cyan(n.runs)} (${o.green(`${n.passed} passed`)}, ${n.failed>0?o.red(`${n.failed} failed`):o.gray(`${n.failed} failed`)})`)),console.log(o.white(` Selectors: ${o.cyan(n.selectors)} tracked`)),console.log(o.white(` Pages: ${o.cyan(n.pages)} discovered`)),console.log(o.white(` Transitions: ${o.cyan(n.transitions)} mapped`)),console.log(o.white(` Insights: ${o.cyan(n.insights||0)} saved`)),e.recentRuns.length>0){console.log(`
17
+ ${a}`),console.log(o.white(" Recent runs:"));for(const t of e.recentRuns){const c=t.passed?o.green("\u2713"):o.red("\u2717"),r=t.duration_ms?o.gray(`${(t.duration_ms/1e3).toFixed(1)}s`):"";console.log(` ${c} ${o.white(t.spec_path)} ${r}`)}}if(e.topSelectors.length>0){console.log(`
18
+ ${a}`),console.log(o.white(" Top selectors:"));for(const t of e.topSelectors){const c=t.success_count+t.failure_count,r=Math.round(t.success_count/c*100);console.log(` ${o.cyan(t.stable_id||"?")} \u2192 ${t.element_desc} (${r}%, ${c} uses)`)}}if(e.log){console.log(`
19
+ ${a}`),console.log(o.white(" Recent commits:"));const t=e.log.split(`
20
+ `).filter(c=>c.startsWith("commit")||c.trim().startsWith("run ")).slice(0,10);for(const c of t)console.log(o.gray(` ${c.trim()}`))}console.log("")}async function f(s){const l=await d();if(!l)return;const e=process.cwd();if(!s.force){console.log(o.yellow(`
21
+ This will permanently delete the memory database.
22
+ `)),console.log(o.white(` Run with --force to confirm: zibby memory reset --force
23
+ `));return}const a=l.resetMemory(e);console.log(a?o.green(`
24
+ Memory database reset.
25
+ `):o.gray(`
26
+ No memory database found.
27
+ `))}async function b(s){const l=await d();if(!l)return;const e=process.cwd(),a=l.getStats(e);if(!a.initialized){console.log(o.yellow(`
28
+ Memory database not initialized.
29
+ `));return}const n=s.maxRuns||50,t=s.maxAge||90;console.log(o.white(`
30
+ Compacting memory (keep last ${n} runs/spec, prune data older than ${t}d)...`));const c=a.counts;if(l.compactMemory(e,{maxRuns:n,maxAgeDays:t}).pruned){const m=l.getStats(e).counts,i=g=>c[g]-m[g];console.log(o.green(" Done.")),i("runs")>0&&console.log(o.gray(` Pruned ${i("runs")} old runs`)),i("selectors")>0&&console.log(o.gray(` Pruned ${i("selectors")} stale selectors`)),i("insights")>0&&console.log(o.gray(` Pruned ${i("insights")} old insights`)),i("transitions")>0&&console.log(o.gray(` Pruned ${i("transitions")} stale transitions`)),console.log(o.gray(` Dolt GC completed
31
+ `))}else console.log(o.gray(` Nothing to compact.
32
+ `))}async function p(){const s=await d();if(!s)return;const l=process.cwd();if(!s.DoltDB.isAvailable()){console.log(o.red(`
33
+ Dolt is not installed.
34
+ `)),console.log(o.white(" Install Dolt:")),console.log(o.gray(" brew install dolt # macOS")),console.log(o.gray(` curl -L https://github.com/dolthub/dolt/releases/latest/download/install.sh | bash # Linux
35
+ `));return}const{created:e,available:a}=s.initMemory(l);e?console.log(o.green(`
36
+ Memory database initialized at .zibby/memory/
37
+ `)):a&&console.log(o.gray(`
38
+ Memory database already initialized.
39
+ `))}export{b as memoryCompactCommand,p as memoryInitCommand,f as memoryResetCommand,h as memoryStatsCommand};
@@ -0,0 +1,4 @@
1
+ import{resolve as p}from"path";import{existsSync as l}from"fs";import t from"chalk";import{readRunCapacityWaitSnapshot as u}from"@zibby/core/utils/run-capacity-queue.js";import{resolveRunCapacityPaths as g}from"@zibby/core/utils/run-capacity-coordinator.js";async function C(r){const n=process.cwd(),c=p(n,r.config||".zibby.config.mjs");let s={paths:{output:".zibby/output",specs:"test-specs",generated:"tests"}};if(l(c))try{const e=(await import(c)).default||{};s={...s,...e,paths:{...s.paths,...e.paths||{}}}}catch(o){console.log(t.yellow(`Could not load config: ${o.message}`))}const{outputAbs:i}=g(n,s),a=u(i);if(console.log(t.bold.cyan(`
2
+ Run capacity wait queue`)),console.log(t.gray(`Output: ${i}
3
+ `)),a.length===0){console.log(t.gray(`(empty \u2014 no CLI processes are waiting for capacity)
4
+ `));return}for(const o of a){const e=[t.cyan(String(o.id||"").slice(0,8)),o.specHint?t.white(o.specHint):"",o.source?t.gray(`[${o.source}]`):"",o.studioTestCaseId?t.gray(`tc:${String(o.studioTestCaseId).slice(0,8)}\u2026`):"",`pid ${o.pid}`].filter(Boolean).join(" ");console.log(e)}console.log()}export{C as runCapacityQueueListCommand};