@zibby/cli 0.1.5

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