@zibby/cli 0.1.25 → 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 -269
  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 -851
  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
@@ -1,851 +0,0 @@
1
- import { mkdir, writeFile, readFile } from 'fs/promises';
2
- import { existsSync } from 'fs';
3
- import { join, resolve, dirname } from 'path';
4
- import { homedir } from 'os';
5
- import inquirer from 'inquirer';
6
- import chalk from 'chalk';
7
- import ora from 'ora';
8
- import { spawn, execSync } from 'child_process';
9
- import { fileURLToPath } from 'url';
10
-
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = dirname(__filename);
13
-
14
- async function checkNpmGlobalBinInPath() {
15
- try {
16
- const isWindows = process.platform === 'win32';
17
-
18
- // Get npm global bin directory
19
- const npmPrefix = execSync('npm config get prefix', { encoding: 'utf-8' }).trim();
20
- const npmGlobalBin = isWindows ? npmPrefix : `${npmPrefix}/bin`;
21
- const npmGlobalBinExpanded = npmGlobalBin.replace(/^~/, homedir());
22
-
23
- // Check if it's in PATH
24
- const pathSeparator = isWindows ? ';' : ':';
25
- const pathDirs = process.env.PATH.split(pathSeparator);
26
- const isInPath = pathDirs.some(dir => {
27
- const normalizedDir = dir.replace(/^~/, homedir());
28
- return normalizedDir === npmGlobalBinExpanded || normalizedDir === npmGlobalBin;
29
- });
30
-
31
- if (isInPath) {
32
- return; // Already configured
33
- }
34
-
35
- // Not in PATH - offer to add it
36
- console.log(chalk.yellow('⚠️ npm global bin not in PATH'));
37
- console.log(chalk.gray(` Location: ${npmGlobalBinExpanded}`));
38
- console.log();
39
-
40
- const { shouldAddPath } = await inquirer.prompt([{
41
- type: 'confirm',
42
- name: 'shouldAddPath',
43
- message: 'Add npm global bin to your shell PATH automatically?',
44
- default: true
45
- }]);
46
-
47
- if (!shouldAddPath) {
48
- if (isWindows) {
49
- console.log(chalk.gray('\n💡 To add manually on Windows:'));
50
- console.log(chalk.gray(' 1. Search "Environment Variables" in Start menu'));
51
- console.log(chalk.gray(' 2. Edit "Path" in User variables'));
52
- console.log(chalk.gray(` 3. Add: ${npmGlobalBinExpanded}\n`));
53
- } else {
54
- console.log(chalk.gray('\n💡 To add manually, run:'));
55
- console.log(chalk.gray(` echo 'export PATH="${npmGlobalBinExpanded}:$PATH"' >> ~/.zshrc`));
56
- console.log(chalk.gray(` source ~/.zshrc\n`));
57
- }
58
- return;
59
- }
60
-
61
- // Windows: Can't auto-add to PATH (requires registry changes)
62
- if (isWindows) {
63
- console.log(chalk.yellow('⚠️ Cannot auto-add PATH on Windows'));
64
- console.log(chalk.gray(' Please add manually:'));
65
- console.log(chalk.gray(' 1. Search "Environment Variables" in Start menu'));
66
- console.log(chalk.gray(' 2. Edit "Path" in User variables'));
67
- console.log(chalk.gray(` 3. Add: ${npmGlobalBinExpanded}\n`));
68
- return;
69
- }
70
-
71
- // Unix/Linux: Auto-add to shell config
72
- const shell = process.env.SHELL || '';
73
- let shellRc = '';
74
-
75
- if (shell.includes('zsh')) {
76
- shellRc = join(homedir(), '.zshrc');
77
- } else if (shell.includes('bash')) {
78
- // Linux often uses .bashrc, macOS uses .bash_profile
79
- shellRc = existsSync(join(homedir(), '.bashrc'))
80
- ? join(homedir(), '.bashrc')
81
- : join(homedir(), '.bash_profile');
82
- } else {
83
- console.log(chalk.yellow(`⚠️ Unknown shell: ${shell}`));
84
- console.log(chalk.gray(' Please add manually:'));
85
- console.log(chalk.gray(` export PATH="${npmGlobalBinExpanded}:$PATH"`));
86
- return;
87
- }
88
-
89
- // Check if already in shell config
90
- if (existsSync(shellRc)) {
91
- const rcContent = await readFile(shellRc, 'utf-8');
92
- if (rcContent.includes(npmGlobalBinExpanded) || (rcContent.includes('npm') && rcContent.includes('global') && rcContent.includes('bin'))) {
93
- console.log(chalk.yellow(`⚠️ PATH entry found in ${shellRc} but not active`));
94
- console.log(chalk.gray(` Run: source ${shellRc}\n`));
95
- return;
96
- }
97
- }
98
-
99
- // Add to shell config
100
- const pathEntry = `\n# npm global bin (added by zibby)\nexport PATH="${npmGlobalBinExpanded}:$PATH"\n`;
101
- await writeFile(shellRc, (existsSync(shellRc) ? await readFile(shellRc, 'utf-8') : '') + pathEntry);
102
-
103
- console.log(chalk.green(`✅ Added to ${shellRc}`));
104
- console.log(chalk.yellow('\n💡 Run this to activate in current session:'));
105
- console.log(chalk.gray(` source ${shellRc}\n`));
106
-
107
- } catch {
108
- // Silently fail - don't block init if this check fails
109
- }
110
- }
111
-
112
- export async function initCommand(projectName, options) {
113
- console.log(chalk.bold.cyan('\n🎭 Welcome to Zibby Test Automation!\n'));
114
-
115
- // Check if npm global bin is in PATH
116
- await checkNpmGlobalBinInPath();
117
-
118
- const targetDir = projectName ? resolve(process.cwd(), projectName) : process.cwd();
119
- const projectNameActual = projectName || 'zibby-tests';
120
- const isNewProject = !!projectName;
121
-
122
- // If creating new project, check if directory exists
123
- if (isNewProject && existsSync(targetDir)) {
124
- console.log(chalk.red(`\n❌ Directory "${projectName}" already exists!\n`));
125
- process.exit(1);
126
- }
127
-
128
- // If in existing directory, check if already initialized (unless --force)
129
- if (!isNewProject && existsSync(join(targetDir, '.zibby.config.mjs')) && !options.force) {
130
- console.log(chalk.yellow('\n⚠️ Zibby is already initialized in this directory!\n'));
131
- console.log(chalk.white('Config file found: .zibby.config.mjs'));
132
- console.log(chalk.gray('Use --force or -f to reinitialize\n'));
133
- process.exit(0);
134
- }
135
-
136
- if (options.force && !isNewProject) {
137
- console.log(chalk.cyan('\nReinitializing Zibby configuration...\n'));
138
- }
139
-
140
- // Build answers from CLI flags or prompt interactively
141
- let answers;
142
-
143
- // Check if all options provided via CLI (non-interactive mode)
144
- const hasAllFlags = options.agent && (options.headed || options.headless);
145
-
146
- if (hasAllFlags) {
147
- // Non-interactive mode - use CLI flags
148
- console.log(chalk.cyan('Setting up with provided options...\n'));
149
- answers = {
150
- agent: options.agent,
151
- browserMode: options.headless ? 'headless' : 'headed',
152
- apiKey: options.apiKey || null,
153
- cloudSync: !!(options.cloudSync || options.apiKey),
154
- };
155
- } else {
156
- // Interactive mode - prompt for missing options
157
- const prompts = [];
158
-
159
- if (!options.agent) {
160
- prompts.push({
161
- type: 'select',
162
- name: 'agent',
163
- message: 'Which AI agent do you prefer?',
164
- choices: [
165
- { name: 'Cursor', value: 'cursor' },
166
- { name: 'Claude (Anthropic)', value: 'claude' },
167
- ],
168
- default: 'cursor',
169
- });
170
- }
171
-
172
- if (!options.headed && !options.headless) {
173
- prompts.push({
174
- type: 'select',
175
- name: 'browserMode',
176
- message: 'Browser mode during live AI execution?',
177
- choices: [
178
- { name: 'Headed - Visible browser (recommended for development)', value: 'headed' },
179
- { name: 'Headless - Hidden browser (for CI/CD)', value: 'headless' },
180
- ],
181
- default: 'headed',
182
- });
183
- }
184
-
185
- if (!options.apiKey) {
186
- prompts.push({
187
- type: 'input',
188
- name: 'apiKey',
189
- message: 'Enable cloud sync? Enter project ZIBBY_API_KEY (or press Enter to skip):',
190
- });
191
- }
192
-
193
- answers = prompts.length > 0 ? await inquirer.prompt(prompts) : {};
194
-
195
- // Merge with CLI options
196
- answers.agent = options.agent || answers.agent;
197
- answers.browserMode = options.headless ? 'headless' : (options.headed ? 'headed' : answers.browserMode);
198
- answers.apiKey = options.apiKey || answers.apiKey;
199
- answers.cloudSync = !!(options.cloudSync || options.apiKey || (answers.apiKey && answers.apiKey.trim()));
200
- }
201
-
202
- // Set Playwright as default (only option for now)
203
- answers.mcp = 'playwright';
204
-
205
- // Automatically setup MCP if Cursor agent selected
206
- answers.setupMcp = (answers.agent === 'cursor');
207
-
208
- const spinner = ora('Setting up Zibby...').start();
209
-
210
- try {
211
- // Create project directory if new project
212
- if (isNewProject) {
213
- await mkdir(targetDir, { recursive: true });
214
- }
215
-
216
- // Create folders
217
- await mkdir(join(targetDir, 'test-specs/examples'), { recursive: true });
218
- await mkdir(join(targetDir, 'tests'), { recursive: true });
219
- await mkdir(join(targetDir, '.zibby/output'), { recursive: true });
220
-
221
- if (options.mem) {
222
- try {
223
- const { initMemory, DoltDB } = await import('@zibby/memory');
224
- if (DoltDB.isAvailable()) {
225
- const { created } = initMemory(targetDir);
226
- if (created) {
227
- spinner.text = 'Initialized test memory database (Dolt)...';
228
- }
229
- } else {
230
- spinner.text = 'Dolt not found — skipping memory database (brew install dolt)';
231
- }
232
- } catch (_e) {
233
- // @zibby/memory not available — skip silently
234
- }
235
- }
236
-
237
- spinner.text = 'Scaffolding workflow graph...';
238
-
239
- // Use Template Factory to get the right template
240
- const { TemplateFactory } = await import('@zibby/core/templates');
241
- const templateName = options.template || 'browser-test-automation'; // Default to browser-test-automation
242
-
243
- try {
244
- const { graphPath, nodesPath, readmePath, resultHandlerPath } = TemplateFactory.getTemplateFiles(templateName);
245
- const targetZibbyDir = join(targetDir, '.zibby');
246
-
247
- // Copy graph.mjs
248
- const graphTemplate = await readFile(graphPath, 'utf-8');
249
- await writeFile(join(targetZibbyDir, 'graph.mjs'), graphTemplate);
250
-
251
- // Copy result-handler.mjs (workflow-specific post-processing)
252
- if (resultHandlerPath) {
253
- const rhTemplate = await readFile(resultHandlerPath, 'utf-8');
254
- await writeFile(join(targetZibbyDir, 'result-handler.mjs'), rhTemplate);
255
- }
256
-
257
- // Copy README
258
- const readmeTemplate = await readFile(readmePath, 'utf-8');
259
- await writeFile(join(targetZibbyDir, 'README.md'), readmeTemplate);
260
-
261
- // Copy nodes directory
262
- await mkdir(join(targetZibbyDir, 'nodes'), { recursive: true });
263
- const { readdirSync } = await import('fs');
264
- const nodeFiles = readdirSync(nodesPath);
265
- for (const file of nodeFiles) {
266
- let content = await readFile(join(nodesPath, file), 'utf-8');
267
-
268
- // If --mem flag NOT used, remove SKILLS.MEMORY from execute-live.mjs
269
- if (!options.mem && file === 'execute-live.mjs') {
270
- content = content.replace(
271
- 'skills: [SKILLS.BROWSER, SKILLS.MEMORY],',
272
- 'skills: [SKILLS.BROWSER],'
273
- );
274
- }
275
-
276
- await writeFile(join(targetZibbyDir, 'nodes', file), content);
277
- }
278
- } catch (error) {
279
- spinner.fail(`Failed to scaffold template: ${error.message}`);
280
- throw error;
281
- }
282
-
283
- spinner.text = 'Generating configuration files...';
284
-
285
- // Always create Zibby config
286
- const configContent = generateConfig(answers, options);
287
- await writeFile(join(targetDir, '.zibby.config.mjs'), configContent);
288
-
289
- // Always create .env.example
290
- const envContent = generateEnvFile(answers, options);
291
- await writeFile(join(targetDir, '.env.example'), envContent);
292
-
293
- // Create/update .env file
294
- const envPath = join(targetDir, '.env');
295
- if (answers.apiKey && answers.apiKey.trim()) {
296
- await createOrUpdateEnvFile(targetDir, answers.apiKey.trim(), answers, options);
297
- } else if (!existsSync(envPath)) {
298
- // Create .env from .env.example if it doesn't exist
299
- await writeFile(envPath, envContent);
300
- } else if (options.force) {
301
- // On force reinit, merge memory vars into existing .env if using --mem
302
- if (options.mem) {
303
- let existingEnv = await readFile(envPath, 'utf-8');
304
- if (!existingEnv.includes('ZIBBY_MEMORY_MAX_RUNS')) {
305
- existingEnv += `\n# Test Memory (Dolt DB) - Auto-compaction settings\n`;
306
- existingEnv += `# ZIBBY_MEMORY_MAX_RUNS=3000 # Max test runs to keep per spec\n`;
307
- existingEnv += `# ZIBBY_MEMORY_MAX_AGE=1095 # Max age in days for stale data (~3 years)\n`;
308
- existingEnv += `# ZIBBY_MEMORY_COMPACT_EVERY=1500 # Auto-compact every N runs (0 to disable)\n`;
309
- await writeFile(envPath, existingEnv);
310
- }
311
- }
312
- }
313
-
314
-
315
- // Create package.json for new projects only (don't modify existing ones)
316
- if (isNewProject) {
317
- const packageJsonContent = generatePackageJson(projectNameActual, answers);
318
- await writeFile(join(targetDir, 'package.json'), packageJsonContent);
319
- }
320
-
321
- // Create .gitignore if doesn't exist
322
- if (!existsSync(join(targetDir, '.gitignore'))) {
323
- const gitignoreContent = generateGitignore();
324
- await writeFile(join(targetDir, '.gitignore'), gitignoreContent);
325
- }
326
-
327
- // Create playwright.config.js if doesn't exist
328
- if (!existsSync(join(targetDir, 'playwright.config.js'))) {
329
- const playwrightConfigContent = generatePlaywrightConfig('on'); // Default: video enabled
330
- await writeFile(join(targetDir, 'playwright.config.js'), playwrightConfigContent);
331
- }
332
-
333
- // Create example spec if doesn't exist
334
- if (!existsSync(join(targetDir, 'test-specs/examples/example-domain.txt'))) {
335
- const exampleSpecContent = generateExampleSpec();
336
- await writeFile(join(targetDir, 'test-specs/examples/example-domain.txt'), exampleSpecContent);
337
- }
338
-
339
- // Create README only for new projects
340
- if (isNewProject) {
341
- const readmeContent = generateReadme(projectNameActual, answers);
342
- await writeFile(join(targetDir, 'README.md'), readmeContent);
343
- }
344
-
345
- spinner.succeed(isNewProject ? 'Project created!' : 'Zibby initialized!');
346
-
347
- // Install dependencies for new projects
348
- if (isNewProject && !options.skipInstall) {
349
- const installSpinner = ora('Installing dependencies...').start();
350
-
351
- await new Promise((resolveFn, reject) => {
352
- const npm = spawn('npm', ['install'], {
353
- cwd: targetDir,
354
- stdio: 'pipe',
355
- });
356
-
357
- npm.on('close', (code) => {
358
- if (code === 0) {
359
- installSpinner.succeed('Dependencies installed!');
360
- resolveFn();
361
- } else {
362
- installSpinner.fail('Failed to install dependencies');
363
- reject(new Error('npm install failed'));
364
- }
365
- });
366
- });
367
- } else if (!isNewProject) {
368
- console.log(chalk.gray('\nMake sure @zibby/cli is installed in your package.json\n'));
369
- }
370
-
371
- // Always check and install Playwright browsers if missing (for both new and existing projects)
372
- // This ensures browsers are available even when reinitializing
373
- if (!options.skipInstall) {
374
- const playwrightSpinner = ora('Installing Playwright browsers...').start();
375
-
376
- await new Promise((resolveFn) => {
377
- const npx = spawn('npx', ['playwright', 'install', 'chromium'], {
378
- cwd: targetDir,
379
- stdio: 'pipe',
380
- });
381
-
382
- let output = '';
383
- npx.stdout.on('data', (data) => {
384
- output += data.toString();
385
- });
386
- npx.stderr.on('data', (data) => {
387
- output += data.toString();
388
- });
389
-
390
- npx.on('close', (code) => {
391
- if (code === 0) {
392
- // Check if it actually installed or was already installed
393
- if (output.includes('already installed') || output.includes('up to date')) {
394
- playwrightSpinner.succeed('Playwright browsers already installed');
395
- } else {
396
- playwrightSpinner.succeed('Playwright browsers installed!');
397
- }
398
- resolveFn();
399
- } else {
400
- playwrightSpinner.warn('Could not verify Playwright browsers');
401
- console.log(chalk.yellow('\n⚠️ If tests fail, run: npx playwright install\n'));
402
- resolveFn(); // Don't fail the whole init
403
- }
404
- });
405
- });
406
- }
407
-
408
- // Setup cursor-agent MCP if requested
409
- if (answers.agent === 'cursor' && answers.setupMcp) {
410
- const mcpSpinner = ora('Setting up Playwright MCP...').start();
411
-
412
- try {
413
- const { setupPlaywrightMcpCommand } = await import('./setup-scripts.js');
414
-
415
- // Use cloudSync from answers (prompt response or flag)
416
- const cloudSync = answers.cloudSync || false;
417
-
418
- // Use browserMode from answers (default to headed)
419
- const isHeaded = answers.browserMode !== 'headless';
420
-
421
- await setupPlaywrightMcpCommand({
422
- headed: isHeaded,
423
- cloudSync,
424
- video: 'on', // Default video recording enabled
425
- viewport: { width: 1280, height: 720 } // Default viewport size
426
- });
427
-
428
- let message = 'Playwright MCP configured';
429
- if (isHeaded) message += ' (headed mode - visible browser)';
430
- else message += ' (headless mode - hidden browser)';
431
- if (cloudSync) message += ' + Zibby MCP (cloud sync)';
432
-
433
- mcpSpinner.succeed(message);
434
-
435
- if (cloudSync && answers.apiKey) {
436
- console.log(chalk.gray('\n ✓ ZIBBY_API_KEY saved to .env\n'));
437
- } else if (cloudSync) {
438
- console.log(chalk.gray('\n Set ZIBBY_API_KEY in .env to enable uploads\n'));
439
- }
440
- } catch (error) {
441
- // Error during MCP setup - check if MCP is already configured
442
- mcpSpinner.fail('MCP setup script failed');
443
- console.log(chalk.yellow(' Check if MCP is already configured:'));
444
- console.log(chalk.gray(' • Open Cursor settings → Check "playwright-official" MCP'));
445
- console.log(chalk.gray(' • Run: cursor-agent mcp list'));
446
- console.log(chalk.gray(` • Or run manually: zibby setup-playwright\n`));
447
- console.log(chalk.gray(` Error: ${error.message}\n`));
448
- }
449
- }
450
-
451
- console.log(chalk.bold.green('\n🎉 All set!\n'));
452
- console.log(chalk.cyan('Try running your first test:'));
453
- if (isNewProject) {
454
- console.log(chalk.white(` cd ${projectName}`));
455
- }
456
- console.log(chalk.white(' zibby run test-specs/examples/example-domain.txt\n'));
457
-
458
- console.log(chalk.cyan('Next steps:'));
459
- if (answers.agent === 'claude') {
460
- let step = 1;
461
- console.log(chalk.white(` ${step++}. ${answers.apiKey ? 'Update' : 'Copy .env.example to .env and add'} ANTHROPIC_API_KEY`));
462
- if (answers.cloudSync && !answers.apiKey) {
463
- console.log(chalk.white(` ${step++}. Add ZIBBY_API_KEY to .env for cloud sync`));
464
- }
465
- console.log(chalk.white(` ${step++}. Write test specs in test-specs/`));
466
- console.log(chalk.white(` ${step++}. Run: npx zibby run <spec-file>\n`));
467
- } else if (answers.agent === 'cursor') {
468
- let step = 1;
469
- if (answers.cloudSync && !answers.apiKey) {
470
- console.log(chalk.white(` ${step++}. Add ZIBBY_API_KEY to .env for cloud sync`));
471
- }
472
- console.log(chalk.white(` ${step++}. Write test specs in test-specs/`));
473
- console.log(chalk.white(` ${step++}. Run: npx zibby run <spec-file>\n`));
474
- }
475
-
476
- } catch (error) {
477
- spinner.fail('Failed to create project');
478
- console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
479
- process.exit(1);
480
- }
481
- }
482
-
483
- function generateConfig(answers, _options = {}) {
484
- // Import here to avoid circular deps - constants are pure values
485
- const DEFAULT_CLAUDE_MODEL = 'auto';
486
- return `export default {
487
- // AI agent settings - specify 'claude' or 'cursor' block
488
- agent: {${answers.agent === 'claude' ? `
489
- // Claude API settings
490
- claude: {
491
- model: '${DEFAULT_CLAUDE_MODEL}', // Options: 'auto', 'sonnet-4.6', 'opus-4.6', 'sonnet-4.5', 'opus-4.5'
492
- maxTokens: 4096,
493
- },
494
-
495
- // OR use Cursor Agent (comment out claude block above, uncomment this):
496
- // cursor: {
497
- // 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', 'gemini-3-pro', 'gemini-3-flash'
498
- // },` : `
499
- // Cursor Agent settings
500
- cursor: {
501
- 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', 'gemini-3-pro', 'gemini-3-flash'
502
- },
503
-
504
- // OR use Claude (comment out cursor block above, uncomment this):
505
- // claude: {
506
- // model: '${DEFAULT_CLAUDE_MODEL}', // Options: 'auto', 'sonnet-4.6', 'opus-4.6', 'sonnet-4.5', 'opus-4.5'
507
- // maxTokens: 4096,
508
- // },`}
509
- strictMode: false, // Set to true if you need reliable structured output enforcement for production workflows (requires authentication)
510
- },
511
-
512
- // Advanced: Override models per node (optional)
513
- // models: {
514
- // default: 'auto', // Fallback for all nodes
515
- // execute_live: 'claude-opus-4', // Override specific node
516
- // },
517
-
518
- // Folder paths
519
- paths: {
520
- specs: 'test-specs', // Where your .txt test specs are
521
- generated: 'tests', // Where generated .spec.js files go
522
- output: '.zibby/output', // Where workflow execution results are saved (default: .zibby/output)
523
- // sessionPrefix: 'run', // Optional: prefix for session folders (e.g., run_1772788458045)
524
- },
525
-
526
- // Context discovery - auto-discovers CONTEXT.md & AGENTS.md files (cascades from root → spec directory)
527
- // Override filenames to search for different files
528
- context: {
529
- filenames: ['CONTEXT.md', 'AGENTS.md'], // Auto-discover these files (walks up from spec directory)
530
- discovery: {
531
- env: \`env-\${process.env.ENV || 'local'}.js\`, // Additional explicit files
532
- // fixtures: 'fixtures.js', // Add more as needed
533
- }
534
- },
535
-
536
- // Video recording (affects playwright.config.js generation)
537
- video: 'on', // Options: 'off', 'on', 'retain-on-failure', 'on-first-retry'
538
-
539
- // Browser viewport size for test execution
540
- // Default: 1280x720 (works well on most screens)
541
- // For larger displays: { width: 1920, height: 1080 }
542
- // For mobile testing: { width: 375, height: 667 } (iPhone SE)
543
- viewport: { width: 1280, height: 720 },
544
-
545
- // Playwright artifacts (screenshots, traces, videos)
546
- // Traces contain EXACT selectors for 100% accurate script generation
547
- // Default: true (stored in test-results/playwright, separate from workflow output)
548
- playwrightArtifacts: true,
549
-
550
- // Cloud sync - auto-upload test results & videos (requires ZIBBY_API_KEY in .env)
551
- cloudSync: ${answers.cloudSync || false}
552
- };
553
- `;
554
- }
555
-
556
- function generateEnvFile(answers, options = {}) {
557
- let content = `# Zibby Test Automation - Environment Variables
558
-
559
- # AI Provider Keys
560
- ${answers.agent === 'claude' ? '# Claude (Anthropic) - Direct API\nANTHROPIC_API_KEY=sk-ant-your_key_here\n' : '# ANTHROPIC_API_KEY=sk-ant-your_key_here\n'}
561
- ${answers.agent === 'cursor' ? '# Cursor Agent (uses cursor-agent CLI)\n# No API key needed if cursor-agent is installed\n# Run: curl https://cursor.com/install -fsS | bash\n' : ''}
562
-
563
- # Zibby Cloud Sync (for uploading test results & videos)
564
- # Get your API key from: https://zibby.app/settings/tokens
565
- # ZIBBY_API_KEY=zby_your_api_key_here
566
-
567
- # Optional: Notifications
568
- # SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
569
- `;
570
-
571
- // Add memory configuration if --mem flag is used
572
- if (options.mem) {
573
- content += `
574
- # Test Memory (Dolt DB) - Auto-compaction settings
575
- # ZIBBY_MEMORY_MAX_RUNS=3000 # Max test runs to keep per spec
576
- # ZIBBY_MEMORY_MAX_AGE=1095 # Max age in days for stale data (~3 years)
577
- # ZIBBY_MEMORY_COMPACT_EVERY=1500 # Auto-compact every N runs (0 to disable)
578
- `;
579
- }
580
-
581
- return content;
582
- }
583
-
584
- async function createOrUpdateEnvFile(targetDir, apiKey, answers, options = {}) {
585
- const envPath = join(targetDir, '.env');
586
- let envContent;
587
-
588
- // Read existing .env if it exists
589
- if (existsSync(envPath)) {
590
- try {
591
- envContent = await readFile(envPath, 'utf-8');
592
-
593
- // Update or add ZIBBY_API_KEY
594
- if (envContent.includes('ZIBBY_API_KEY=')) {
595
- envContent = envContent.replace(
596
- /ZIBBY_API_KEY=.*/g,
597
- `ZIBBY_API_KEY=${apiKey}`
598
- );
599
- } else {
600
- envContent += `\n# Zibby Cloud Sync\nZIBBY_API_KEY=${apiKey}\n`;
601
- }
602
- } catch (_error) {
603
- envContent = generateEnvFileWithKey(answers, apiKey, options);
604
- }
605
- } else {
606
- envContent = generateEnvFileWithKey(answers, apiKey, options);
607
- }
608
-
609
- await writeFile(envPath, envContent);
610
- }
611
-
612
- function generateEnvFileWithKey(answers, apiKey, options = {}) {
613
- let content = `# Zibby Test Automation - Environment Variables
614
-
615
- # AI Provider Keys
616
- ${answers.agent === 'claude' ? 'ANTHROPIC_API_KEY=sk-ant-your_key_here\n' : '# ANTHROPIC_API_KEY=sk-ant-your_key_here\n'}
617
-
618
- # Zibby Cloud Sync
619
- ZIBBY_API_KEY=${apiKey}
620
-
621
- # Optional: Notifications
622
- # SLACK_WEBHOOK_URL=https://hooks.slack.com/services/YOUR/WEBHOOK/URL
623
- `;
624
-
625
- // Add memory configuration if --mem flag is used
626
- if (options.mem) {
627
- content += `
628
- # Test Memory (Dolt DB) - Auto-compaction settings
629
- # ZIBBY_MEMORY_MAX_RUNS=3000 # Max test runs to keep per spec
630
- # ZIBBY_MEMORY_MAX_AGE=1095 # Max age in days for stale data (~3 years)
631
- # ZIBBY_MEMORY_COMPACT_EVERY=1500 # Auto-compact every N runs (0 to disable)
632
- `;
633
- }
634
-
635
- return content;
636
- }
637
-
638
- function generatePackageJson(projectName, _answers) {
639
- return JSON.stringify({
640
- name: projectName,
641
- version: '1.0.0',
642
- type: 'module',
643
- private: true,
644
- scripts: {
645
- 'test:spec': 'zibby run',
646
- 'test': 'playwright test',
647
- 'test:headed': 'playwright test --headed'
648
- },
649
- dependencies: {
650
- '@zibby/cli': '^0.1.25',
651
- '@zibby/core': '^0.1.20'
652
- },
653
- devDependencies: {
654
- '@playwright/test': '^1.49.0',
655
- 'dotenv': '^17.2.3'
656
- }
657
- }, null, 2);
658
- }
659
-
660
- function generateGitignore() {
661
- return `# Dependencies
662
- node_modules/
663
-
664
- # Test artifacts
665
- .zibby/output/
666
- playwright-report/
667
- tests/
668
- *.webm
669
- *.mp4
670
-
671
- # Environment variables
672
- .env
673
-
674
- # OS
675
- .DS_Store
676
- Thumbs.db
677
-
678
- # IDE
679
- .vscode/
680
- .idea/
681
-
682
- # Zibby cache
683
- .zibby/cache/
684
- .zibby/tenancy.json
685
-
686
- # Zibby memory (Dolt DB synced separately via dolt remote, not git)
687
- .zibby/memory/
688
- .zibby/memory-context.md
689
- `;
690
- }
691
-
692
- function generatePlaywrightConfig(_videoOption = 'off', outputDir = null) {
693
- const outputDirLine = outputDir ? ` outputDir: '${outputDir}',\n` : ` outputDir: 'test-results/playwright', // Keep Playwright artifacts separate from Zibby workflow output\n`;
694
-
695
- return `import { defineConfig} from '@playwright/test';
696
-
697
- export default defineConfig({
698
- testDir: './tests',
699
- ${outputDirLine} timeout: 30000,
700
- retries: 0,
701
- workers: 1,
702
-
703
- use: {
704
- headless: process.env.PLAYWRIGHT_HEADLESS === '1' ? true : false,
705
- viewport: { width: 1280, height: 720 },
706
- screenshot: 'off',
707
- video: 'off',
708
- },
709
-
710
- reporter: [
711
- ['html'],
712
- ['list']
713
- ],
714
- });
715
- `;
716
- }
717
-
718
- function generateExampleSpec() {
719
- return `Test Specification: Example Domain Navigation
720
- ==============================================
721
-
722
- Application: Example Domain
723
- URL: https://example.com
724
- Feature: Basic navigation and link verification
725
-
726
- Test Objective:
727
- ---------------
728
- Verify that the example.com website loads correctly, displays expected content,
729
- and navigational links function as intended.
730
-
731
- Test Steps:
732
- -----------
733
- 1. Navigate to https://example.com
734
- 2. Verify the page title contains "Example Domain"
735
- 3. Verify the main heading is visible and contains "Example Domain"
736
- 4. Locate and verify the "More information..." link is present
737
- 5. Click the "More information..." link
738
- 6. Verify navigation to IANA domain information page
739
- 7. Verify the new page URL contains "iana.org"
740
- 8. Verify the page loaded successfully (no errors)
741
-
742
- Expected Results:
743
- -----------------
744
- - Initial page loads within 3 seconds
745
- - "Example Domain" heading is clearly visible
746
- - "More information..." link is clickable and visible
747
- - Navigation to IANA page succeeds
748
- - No console errors or network failures
749
- - Page title updates after navigation
750
-
751
- Notes:
752
- ------
753
- - Uses a stable, public domain (example.com) maintained by IANA
754
- - No authentication required
755
- - Suitable for CI/CD smoke testing
756
- `;
757
- }
758
-
759
- function generateReadme(projectName, answers) {
760
- return `# ${projectName}
761
-
762
- AI-powered test automation with Zibby.
763
-
764
- ## Setup
765
-
766
- 1. Install dependencies:
767
- \`\`\`bash
768
- npm install
769
- \`\`\`
770
-
771
- 2. Configure environment:
772
- \`\`\`bash
773
- cp .env.example .env
774
- # Edit .env and add your ${answers.agent === 'claude' ? 'ANTHROPIC_API_KEY' : 'OPENAI_API_KEY'}
775
- \`\`\`
776
-
777
- 3. Run example test:
778
- \`\`\`bash
779
- npx zibby run test-specs/examples/example-domain.txt
780
- \`\`\`
781
-
782
- ## Usage
783
-
784
- ### Run Test Specs
785
-
786
- \`\`\`bash
787
- # Run a test spec (executes + generates + verifies)
788
- npx zibby run test-specs/auth/login.txt
789
-
790
- # Run in headless mode
791
- npx zibby run test-specs/auth/login.txt --headless
792
- \`\`\`
793
-
794
- ### Write Test Specs
795
-
796
- Create test specifications in \`test-specs/\`:
797
-
798
- \`\`\`
799
- test-specs/
800
- auth/
801
- login.txt
802
- logout.txt
803
- dashboard/
804
- overview.txt
805
- \`\`\`
806
-
807
- ### Run Generated Tests
808
-
809
- \`\`\`bash
810
- # Run all generated tests
811
- npx playwright test
812
-
813
- # Run specific test
814
- npx playwright test tests/auth/login.spec.js
815
-
816
- # Run with UI
817
- npx playwright test --ui
818
- \`\`\`
819
-
820
- ## Configuration
821
-
822
- Edit \`.zibby.config.mjs\` to customize:
823
- - Agent settings (model, temperature)
824
- - Browser settings (headless, viewport)
825
- - Cloud sync${answers.cloudSync ? ' (enabled)' : ' (disabled)'}
826
- - Self-healing behavior
827
-
828
- ## Project Structure
829
-
830
- \`\`\`
831
- ${projectName}/
832
- ├── .zibby/
833
- │ ├── graph.mjs # Workflow definition
834
- │ ├── nodes/ # Custom nodes
835
- │ └── output/ # Workflow execution results (gitignored)
836
- │ └── sessions/ # Session artifacts & recordings
837
- ├── .zibby.config.mjs # Configuration
838
- ├── .env # API keys (gitignored)
839
- ├── test-specs/ # Test specifications (committed)
840
- │ └── examples/
841
- │ └── example-domain.txt
842
- └── tests/ # Generated tests (gitignored)
843
- \`\`\`
844
-
845
- ## Learn More
846
-
847
- - Documentation: https://docs.zibby.dev
848
- - Examples: https://github.com/zibby/examples
849
- `;
850
- }
851
-