bangonit 0.3.0 → 0.3.1

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 (49) hide show
  1. package/README.md +69 -37
  2. package/app/desktopapp/dist/main/index.js +10 -161
  3. package/app/desktopapp/dist/main/ipc.js +0 -20
  4. package/app/desktopapp/dist/main/preload.js +0 -2
  5. package/app/desktopapp/dist/main/tabs.js +0 -10
  6. package/app/desktopapp/dist/shared/args.js +21 -0
  7. package/app/desktopapp/package.json +1 -1
  8. package/app/replay/dist/replay.css +1 -1
  9. package/app/replay/dist/replay.js +20 -20
  10. package/app/webapp/.next/BUILD_ID +1 -1
  11. package/app/webapp/.next/app-build-manifest.json +2 -2
  12. package/app/webapp/.next/app-path-routes-manifest.json +1 -1
  13. package/app/webapp/.next/build-manifest.json +2 -2
  14. package/app/webapp/.next/next-minimal-server.js.nft.json +1 -1
  15. package/app/webapp/.next/next-server.js.nft.json +1 -1
  16. package/app/webapp/.next/prerender-manifest.json +1 -1
  17. package/app/webapp/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  18. package/app/webapp/.next/server/app/_not-found.html +1 -1
  19. package/app/webapp/.next/server/app/_not-found.rsc +1 -1
  20. package/app/webapp/.next/server/app/app/page.js +3 -7
  21. package/app/webapp/.next/server/app/app/page_client-reference-manifest.js +1 -1
  22. package/app/webapp/.next/server/app/app.html +1 -1
  23. package/app/webapp/.next/server/app/app.rsc +2 -2
  24. package/app/webapp/.next/server/app/index.html +1 -1
  25. package/app/webapp/.next/server/app/index.rsc +1 -1
  26. package/app/webapp/.next/server/app/page_client-reference-manifest.js +1 -1
  27. package/app/webapp/.next/server/app-paths-manifest.json +2 -2
  28. package/app/webapp/.next/server/chunks/708.js +1 -1
  29. package/app/webapp/.next/server/functions-config-manifest.json +1 -1
  30. package/app/webapp/.next/server/pages/404.html +1 -1
  31. package/app/webapp/.next/server/pages/500.html +1 -1
  32. package/app/webapp/.next/server/server-reference-manifest.json +1 -1
  33. package/app/webapp/.next/static/chunks/app/app/page-0e096497dcb81dae.js +1 -0
  34. package/app/webapp/.next/static/css/{869d3ff23c36c4b5.css → 38219627f55424f2.css} +1 -1
  35. package/app/webapp/.next/trace +2 -2
  36. package/app/webapp/package.json +7 -2
  37. package/app/webapp/src/shared/api/chat.ts +2 -11
  38. package/app/webapp/src/shared/components/AppShell.tsx +21 -1
  39. package/app/webapp/src/shared/components/SessionView.tsx +37 -65
  40. package/app/webapp/src/shared/lib/browser/mouse.ts +1 -1
  41. package/app/webapp/src/shared/lib/browser/recorder.ts +3 -3
  42. package/app/webapp/src/shared/types/global.d.ts +0 -3
  43. package/bin/app/desktopapp/src/shared/args.js +21 -0
  44. package/bin/bangonit.js +259 -96
  45. package/bin/src/cli/bangonit.js +767 -0
  46. package/package.json +6 -4
  47. package/app/webapp/.next/static/chunks/app/app/page-d38c1e48d37def82.js +0 -1
  48. /package/app/webapp/.next/static/{TaLpPsk5rC30wNNcyfUN3 → Qq0OvlQijtcR84Dg9Dgp0}/_buildManifest.js +0 -0
  49. /package/app/webapp/.next/static/{TaLpPsk5rC30wNNcyfUN3 → Qq0OvlQijtcR84Dg9Dgp0}/_ssgManifest.js +0 -0
package/bin/bangonit.js CHANGED
@@ -267,6 +267,16 @@ function createPrompter() {
267
267
  };
268
268
  }
269
269
  // --- init command ---
270
+ // Common timezone offsets from UTC (standard time)
271
+ const TIMEZONE_OFFSETS = {
272
+ "us/eastern": -5, "us/central": -6, "us/mountain": -7, "us/pacific": -8,
273
+ "europe/london": 0, "europe/berlin": 1, "europe/paris": 1,
274
+ "asia/tokyo": 9, "asia/shanghai": 8, "asia/kolkata": 5,
275
+ "australia/sydney": 11,
276
+ };
277
+ function localHourToUtc(localHour, utcOffset) {
278
+ return ((localHour - utcOffset) % 24 + 24) % 24;
279
+ }
270
280
  async function initProject() {
271
281
  const p = createPrompter();
272
282
  console.log(`\n ${c.bold}${c.magenta}Bang On It! init${c.reset}\n`);
@@ -282,7 +292,7 @@ async function initProject() {
282
292
  s3Region = await p.ask("S3 region", "us-east-1");
283
293
  s3Prefix = await p.ask("S3 prefix", "bangonit");
284
294
  }
285
- p.close();
295
+ // --- Config file ---
286
296
  let toml = `testplans = "${testplans}"\n`;
287
297
  toml += `recordings_dir = "${recordingsDir}"\n`;
288
298
  if (apiKey) {
@@ -304,58 +314,148 @@ async function initProject() {
304
314
  }
305
315
  const bangDir = path.join(process.cwd(), ".bangonit");
306
316
  fs.mkdirSync(bangDir, { recursive: true });
307
- const outPath = path.join(bangDir, "config.toml");
308
- fs.writeFileSync(outPath, toml);
309
- console.log(`\n ${c.green}Created${c.reset} .bangonit/config.toml\n`);
310
- const testDir = path.join(process.cwd(), testplans);
311
- if (!fs.existsSync(testDir)) {
312
- fs.mkdirSync(testDir, { recursive: true });
313
- console.log(` ${c.green}Created${c.reset} ${testplans}/\n`);
314
- }
315
- }
316
- // --- init-ci command ---
317
- async function initCi() {
318
- const p = createPrompter();
319
- console.log(`\n ${c.bold}${c.magenta}Bang On It! CI Setup${c.reset}\n`);
320
- const baseImage = await p.ask("GitHub Actions runner", "ubuntu-latest");
321
- const nodeVersion = await p.ask("Node.js version", "20");
322
- const setupCommand = await p.ask("Setup command (install deps, build, etc.)", "npm install && npm run build");
323
- const startCommand = await p.ask("Command to start your web server", "npm start &");
324
- const waitUrl = await p.ask("URL to wait for before testing", "http://localhost:3000");
325
- const testDir = await p.ask("Test plan directory", "testplans");
326
- const triggerInput = await p.askChoice("Trigger on", ["pr", "push", "both"], "both");
327
- const timeout = await p.ask("Test timeout in seconds", "300");
328
- p.close();
329
- const trigger = triggerInput === "pr" ? "pull_request" :
330
- triggerInput === "push" ? "push" :
331
- "both";
332
- let triggerYaml;
333
- if (trigger === "both") {
334
- triggerYaml = `on:
335
- push:
336
- branches: [main, master]
337
- pull_request:`;
338
- }
339
- else if (trigger === "pull_request") {
340
- triggerYaml = `on:
341
- pull_request:`;
342
- }
343
- else {
344
- triggerYaml = `on:
345
- push:
346
- branches: [main, master]`;
347
- }
348
- const workflow = `name: Bang On It! Tests
317
+ const configOutPath = path.join(bangDir, "config.toml");
318
+ fs.writeFileSync(configOutPath, toml);
319
+ console.log(`\n ${c.green}Created${c.reset} .bangonit/config.toml`);
320
+ // --- Test plan directories ---
321
+ const testplanBase = path.join(process.cwd(), testplans);
322
+ const dirs = ["smoke", "acceptance", "regression"];
323
+ for (const dir of dirs) {
324
+ const dirPath = path.join(testplanBase, dir);
325
+ fs.mkdirSync(dirPath, { recursive: true });
326
+ const gitkeep = path.join(dirPath, ".gitkeep");
327
+ if (!fs.existsSync(gitkeep))
328
+ fs.writeFileSync(gitkeep, "");
329
+ console.log(` ${c.green}Created${c.reset} ${testplans}/${dir}/`);
330
+ }
331
+ // Write example smoke test
332
+ const smokeExample = path.join(testplanBase, "smoke", "homepage.md");
333
+ if (!fs.existsSync(smokeExample)) {
334
+ fs.writeFileSync(smokeExample, `---
335
+ name: Homepage loads
336
+ ---
349
337
 
350
- ${triggerYaml}
338
+ ## Steps
339
+ 1. Navigate to http://localhost:3000
340
+ 2. Verify the page loads with a heading
341
+ 3. Verify there are no console errors
342
+ `);
343
+ console.log(` ${c.green}Created${c.reset} ${testplans}/smoke/homepage.md`);
344
+ }
345
+ // --- Claude Code skills ---
346
+ const claudeSkillsDir = path.join(process.cwd(), ".claude", "skills");
347
+ const testSkillDir = path.join(claudeSkillsDir, "test");
348
+ fs.mkdirSync(testSkillDir, { recursive: true });
349
+ fs.writeFileSync(path.join(testSkillDir, "SKILL.md"), `---
350
+ name: test
351
+ description: Run Bang On It! E2E tests locally. Pass test plan files or a filter as $ARGUMENTS (e.g. "testplans/smoke/" or "-t login"). With no arguments, runs all test plans.
352
+ tools: Bash, Read
353
+ ---
351
354
 
352
- jobs:
353
- test:
354
- runs-on: ${baseImage}
355
- timeout-minutes: ${Math.ceil(parseInt(timeout) / 60) + 5}
356
- env:
357
- ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
355
+ # Run E2E Tests
356
+
357
+ Run Bang On It! end-to-end tests locally.
358
+
359
+ **Arguments:** $ARGUMENTS
360
+
361
+ ## Instructions
358
362
 
363
+ 1. If $ARGUMENTS is empty, run all test plans:
364
+ \`\`\`bash
365
+ boi run --record
366
+ \`\`\`
367
+
368
+ 2. If $ARGUMENTS contains file paths or directories, run those:
369
+ \`\`\`bash
370
+ boi run $ARGUMENTS --record
371
+ \`\`\`
372
+
373
+ 3. If $ARGUMENTS contains a filter (e.g. "login", "checkout"), run with filter:
374
+ \`\`\`bash
375
+ boi run -t $ARGUMENTS --record
376
+ \`\`\`
377
+
378
+ 4. Wait for tests to complete. Report results and recording paths.
379
+
380
+ 5. If tests fail, read the test plan file and the output to diagnose the failure. Suggest whether the test plan needs updating or there's a real bug.
381
+ `);
382
+ console.log(` ${c.green}Created${c.reset} .claude/skills/test/SKILL.md`);
383
+ const createTestSkillDir = path.join(claudeSkillsDir, "create-test");
384
+ fs.mkdirSync(createTestSkillDir, { recursive: true });
385
+ fs.writeFileSync(path.join(createTestSkillDir, "SKILL.md"), `---
386
+ name: create-test
387
+ description: Create new Bang On It! test plan(s). Pass a description of what to test as $ARGUMENTS, or omit to auto-generate from git changes.
388
+ tools: Bash, Read, Write, Glob, Grep
389
+ ---
390
+
391
+ # Create Test Plan
392
+
393
+ Create new Bang On It! test plan files.
394
+
395
+ **What to test:** $ARGUMENTS
396
+
397
+ ## Instructions
398
+
399
+ ### Step 0: Determine what to test
400
+
401
+ - If $ARGUMENTS is provided, use it as the description of what to test.
402
+ - If $ARGUMENTS is empty, auto-discover from git changes:
403
+ 1. Run \`git log master..HEAD --oneline\` and \`git diff master...HEAD --stat\` to see what changed on this branch.
404
+ 2. If no branch divergence, run \`git diff HEAD --stat\` and \`git diff HEAD\` for uncommitted changes.
405
+ 3. If still nothing, run \`git log -1 --format="%H %s"\` and \`git show HEAD --stat\` for the latest commit.
406
+ 4. Analyze the changes and create test plan(s) covering them. Bug fixes get regression tests, new features get acceptance tests.
407
+ 5. Skip changes that are already covered by existing test plans, pure refactors, docs, CI, or dependency updates.
408
+
409
+ ### Step 1: Determine which directory the test belongs in
410
+ - \`${testplans}/smoke/\` — Quick sanity checks (app loads, critical path works). Keep smoke tests minimal — they run on every commit so they must be fast. Only add here if it tests truly fundamental functionality. Prefer acceptance/ for most tests.
411
+ - \`${testplans}/acceptance/\` — Core user journeys and happy paths. This is the default for most new tests.
412
+ - \`${testplans}/regression/\` — Bug fixes and edge cases. Use when the description references a bug or issue.
413
+
414
+ 2. Read existing test plans in that directory to understand conventions:
415
+ \`\`\`bash
416
+ ls ${testplans}/smoke/ ${testplans}/acceptance/ ${testplans}/regression/
417
+ \`\`\`
418
+
419
+ 3. Read the codebase to understand what UI elements and flows are involved. Look at routes, components, and pages relevant to the test.
420
+
421
+ 4. Create the test plan file:
422
+ - Filename: kebab-case, e.g. \`password-reset.md\`
423
+ - Use this format:
424
+
425
+ \`\`\`markdown
426
+ ---
427
+ name: Descriptive test name
428
+ retries: 1
429
+ ---
430
+
431
+ ## Steps
432
+ 1. Navigate to the relevant page
433
+ 2. Perform the action being tested
434
+ 3. Verify the expected outcome
435
+ \`\`\`
436
+
437
+ 5. Keep steps concise and actionable. Write from the user's perspective — describe what to click, type, and verify. Don't reference CSS selectors or implementation details.
438
+
439
+ 6. Output the path to the created file.
440
+ `);
441
+ console.log(` ${c.green}Created${c.reset} .claude/skills/create-test/SKILL.md`);
442
+ // --- CI setup ---
443
+ console.log("");
444
+ const setupCi = await p.askChoice("Set up GitHub Actions CI?", ["y", "n"], "y");
445
+ if (setupCi === "y") {
446
+ console.log("");
447
+ const baseImage = await p.ask("GitHub Actions runner", "ubuntu-latest");
448
+ const nodeVersion = await p.ask("Node.js version", "20");
449
+ const setupCommand = await p.ask("Setup command", "npm install && npm run build");
450
+ const startCommand = await p.ask("Command to start your web server", "npm start &");
451
+ const waitUrl = await p.ask("URL to wait for before testing", "http://localhost:3000");
452
+ const timeout = await p.ask("Test timeout in seconds", "300");
453
+ const tzNames = Object.keys(TIMEZONE_OFFSETS).join(", ");
454
+ const tz = await p.ask(`Timezone for full run (${tzNames})`, "us/eastern");
455
+ const utcOffset = TIMEZONE_OFFSETS[tz.toLowerCase()] ?? -5;
456
+ const fullUtcHour = localHourToUtc(18, utcOffset);
457
+ const timeoutMinutes = Math.ceil(parseInt(timeout) / 60) + 5;
458
+ const stepsYaml = `
359
459
  steps:
360
460
  - uses: actions/checkout@v4
361
461
 
@@ -378,12 +478,25 @@ jobs:
378
478
  run: npx wait-on ${waitUrl} --timeout 30000
379
479
 
380
480
  - name: Install bangonit
381
- run: npm install -g bangonit
481
+ run: npm install -g bangonit`;
482
+ const smokeWorkflow = `name: "Bang On It! Smoke Tests"
382
483
 
383
- - name: Run tests
484
+ on:
485
+ push:
486
+ branches: [main, master]
487
+ pull_request:
488
+
489
+ jobs:
490
+ smoke:
491
+ runs-on: ${baseImage}
492
+ timeout-minutes: ${timeoutMinutes}
493
+ env:
494
+ ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
495
+ ${stepsYaml}
496
+
497
+ - name: Run smoke tests
384
498
  run: |
385
- xvfb-run --auto-servernum boi run ${testDir}/*.md \\
386
- --headless --exit \\
499
+ xvfb-run --auto-servernum boi run ${testplans}/smoke/ \\
387
500
  --timeout ${timeout} \\
388
501
  --output bangonit-output.json --record
389
502
 
@@ -391,19 +504,57 @@ jobs:
391
504
  if: always()
392
505
  uses: actions/upload-artifact@v4
393
506
  with:
394
- name: bangonit-results
507
+ name: bangonit-smoke-results
395
508
  path: |
396
509
  bangonit-output.json
397
510
  recordings/
398
511
  if-no-files-found: ignore
399
512
  `;
400
- const outDir = path.join(process.cwd(), ".github", "workflows");
401
- fs.mkdirSync(outDir, { recursive: true });
402
- const outPath = path.join(outDir, "bangonit.yml");
403
- fs.writeFileSync(outPath, workflow);
404
- console.log(`\n ${c.green}Created${c.reset} ${path.relative(process.cwd(), outPath)}\n`);
405
- console.log(` ${c.yellow}Required GitHub secrets:${c.reset}`);
406
- console.log(` ${c.dim} ANTHROPIC_API_KEY ${c.reset}— your Anthropic API key`);
513
+ const fullWorkflow = `name: "Bang On It! Full Tests"
514
+
515
+ on:
516
+ schedule:
517
+ - cron: '0 ${fullUtcHour} * * *'
518
+ workflow_dispatch:
519
+
520
+ jobs:
521
+ full:
522
+ runs-on: ${baseImage}
523
+ timeout-minutes: ${timeoutMinutes * 3}
524
+ env:
525
+ ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
526
+ ${stepsYaml}
527
+
528
+ - name: Run all tests
529
+ run: |
530
+ xvfb-run --auto-servernum boi run \\
531
+ --timeout ${timeout} \\
532
+ --output bangonit-output.json --record
533
+
534
+ - name: Upload test results
535
+ if: always()
536
+ uses: actions/upload-artifact@v4
537
+ with:
538
+ name: bangonit-full-results
539
+ path: |
540
+ bangonit-output.json
541
+ recordings/
542
+ if-no-files-found: ignore
543
+ `;
544
+ const outDir = path.join(process.cwd(), ".github", "workflows");
545
+ fs.mkdirSync(outDir, { recursive: true });
546
+ const smokePath = path.join(outDir, "bangonit-smoke.yml");
547
+ fs.writeFileSync(smokePath, smokeWorkflow);
548
+ console.log(`\n ${c.green}Created${c.reset} ${path.relative(process.cwd(), smokePath)}`);
549
+ const fullPath = path.join(outDir, "bangonit-full.yml");
550
+ fs.writeFileSync(fullPath, fullWorkflow);
551
+ console.log(` ${c.green}Created${c.reset} ${path.relative(process.cwd(), fullPath)}`);
552
+ console.log(`\n ${c.yellow}Required GitHub secret:${c.reset}`);
553
+ console.log(` ${c.dim} ANTHROPIC_API_KEY${c.reset} — your Anthropic API key`);
554
+ console.log(`\n ${c.dim}Smoke tests run on every push/PR.${c.reset}`);
555
+ console.log(` ${c.dim}Full tests run all test plans daily at 6pm ${tz} (${fullUtcHour}:00 UTC).${c.reset}`);
556
+ }
557
+ p.close();
407
558
  console.log("");
408
559
  }
409
560
  async function run(argv, config) {
@@ -418,6 +569,13 @@ async function run(argv, config) {
418
569
  const recordingsDir = config.recordings_dir
419
570
  ? path.resolve(process.cwd(), config.recordings_dir)
420
571
  : path.join(process.cwd(), "recordings");
572
+ // Validate test plan files exist before launching anything
573
+ for (const file of argv.files) {
574
+ const absPath = path.isAbsolute(file) ? file : path.join(process.cwd(), file);
575
+ if (!fs.existsSync(absPath)) {
576
+ die(`Test plan file not found: ${file}`);
577
+ }
578
+ }
421
579
  // Discover test plans if no files/plan/auto specified
422
580
  const files = [...argv.files];
423
581
  if (files.length === 0 && !argv.plan && !argv.auto && config.testplans) {
@@ -433,30 +591,31 @@ async function run(argv, config) {
433
591
  else if (files.length === 0 && !argv.plan && !argv.auto && argv.filter) {
434
592
  die(`--filter requires a testplans directory configured in .bangonit/config.toml`);
435
593
  }
436
- // Build args to forward to Electron
437
- const electronArgs = [...files];
438
- if (argv.plan)
439
- electronArgs.push("--plan", argv.plan);
440
- if (argv.auto)
441
- electronArgs.push("--auto");
442
- if (argv.prompt)
443
- electronArgs.push("--prompt", argv.prompt);
444
- if (argv.record)
445
- electronArgs.push("--record", "--recordings-dir", recordingsDir);
446
- if (argv.headless)
447
- electronArgs.push("--headless");
448
- if (argv.exit)
449
- electronArgs.push("--exit");
450
- if (argv.json)
451
- electronArgs.push("--json");
452
- if (argv.console)
453
- electronArgs.push("--console");
454
- if (argv.output)
455
- electronArgs.push("--output", argv.output);
456
- if (argv.concurrency != null)
457
- electronArgs.push("--concurrency", String(argv.concurrency));
458
- if (argv.timeout != null)
459
- electronArgs.push("--timeout", String(argv.timeout));
594
+ // Hint when launching interactive UI with no config
595
+ if (files.length === 0 && !argv.plan && !argv.auto) {
596
+ if (!config.testplans) {
597
+ console.log(`${c.dim}No test plans specified. Launching interactive UI.${c.reset}`);
598
+ console.log(`${c.dim}Tip: Run ${c.reset}boi init${c.dim} to set up a config file, or pass test plan files directly.${c.reset}\n`);
599
+ }
600
+ }
601
+ // Build structured args for Electron (passed via BANGONIT_ARGS env var)
602
+ const electronArgs = {
603
+ testPlanFiles: files,
604
+ headless: argv.headless,
605
+ exit: argv.exit,
606
+ json: argv.json,
607
+ console: argv.console,
608
+ record: argv.record,
609
+ retries: argv.retries ?? 0,
610
+ auto: argv.auto,
611
+ output: argv.output || null,
612
+ plan: argv.plan || null,
613
+ prompt: argv.prompt || null,
614
+ concurrency: argv.concurrency ?? 1,
615
+ timeout: argv.timeout ?? 0,
616
+ cwd: process.cwd(),
617
+ recordingsDir: argv.record ? recordingsDir : null,
618
+ };
460
619
  const PORT = await getFreePort();
461
620
  fs.mkdirSync(LOGS_DIR, { recursive: true });
462
621
  const nextDir = path.join(WEBAPP_DIR, ".next");
@@ -479,7 +638,15 @@ async function run(argv, config) {
479
638
  const webappLog = fs.createWriteStream(path.join(LOGS_DIR, "webapp.log"));
480
639
  webappProc.stdout.pipe(webappLog);
481
640
  webappProc.stderr.pipe(webappLog);
641
+ let webappCrashed = false;
642
+ webappProc.on("exit", (code) => {
643
+ if (!webappCrashed && code !== null && code !== 0) {
644
+ webappCrashed = true;
645
+ die(`Webapp server crashed (exit code ${code}). Check logs/webapp.log for details.`);
646
+ }
647
+ });
482
648
  const cleanup = () => {
649
+ webappCrashed = true; // suppress crash message during normal shutdown
483
650
  try {
484
651
  webappProc.kill();
485
652
  }
@@ -512,15 +679,11 @@ async function run(argv, config) {
512
679
  die("Error: electron not found. Run `npm install` in app/desktopapp.");
513
680
  }
514
681
  }
515
- const finalArgs = [
516
- ".",
517
- ...electronArgs,
518
- "--cwd", process.cwd(),
519
- ];
520
- const electronProc = (0, child_process_1.spawn)(electronPath, finalArgs, {
682
+ const electronProc = (0, child_process_1.spawn)(electronPath, ["."], {
521
683
  cwd: DESKTOP_DIR,
522
684
  env: {
523
685
  ...process.env,
686
+ BANGONIT_ARGS: JSON.stringify(electronArgs),
524
687
  WEBAPP_URL: `http://localhost:${PORT}/app`,
525
688
  NODE_ENV: process.env.NODE_ENV || "production",
526
689
  },
@@ -560,8 +723,7 @@ const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
560
723
  (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
561
724
  .scriptName("boi")
562
725
  .usage("Usage: $0 <command> [options]")
563
- .command("init", "Create a .bangonit/config.toml config file", {}, () => { initProject(); })
564
- .command("init-ci", "Generate a GitHub Actions workflow", {}, () => { initCi(); })
726
+ .command("init", "Set up Bang On It! (config, test plans, and optionally CI)", {}, () => { initProject(); })
565
727
  .command(["run [files..]", "$0"], "Run test plans (or launch interactive UI)", (y) => y
566
728
  .positional("files", { type: "string", array: true, default: [], describe: "Test plan files" })
567
729
  .option("filter", { alias: "t", type: "string", describe: "Filter test plans by name substring" })
@@ -570,6 +732,7 @@ const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
570
732
  .option("auto", { type: "boolean", default: false, describe: "Auto-generate test plan from git state" })
571
733
  .option("prompt", { type: "string", describe: "Additional instructions appended to test plan" })
572
734
  .option("record", { type: "boolean", default: false, describe: "Record session replay" })
735
+ .option("retries", { type: "number", describe: "Retry failed tests N times (overrides test plan frontmatter)" })
573
736
  .option("headless", { type: "boolean", default: ciDefaults.headless ?? false, describe: "Run without showing the browser window" })
574
737
  .option("exit", { type: "boolean", default: ciDefaults.exit ?? false, describe: "Exit immediately after tests complete" })
575
738
  .option("json", { type: "boolean", default: false, describe: "Stream NDJSON events to stdout" })
@@ -585,6 +748,7 @@ const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
585
748
  auto: argv.auto,
586
749
  prompt: argv.prompt,
587
750
  record: argv.record,
751
+ retries: argv.retries,
588
752
  headless: argv.headless,
589
753
  exit: argv.exit,
590
754
  json: argv.json,
@@ -599,8 +763,7 @@ const ciDefaults = is_ci_1.default ? { headless: true, exit: true } : {};
599
763
  .example("$0 run --auto", "Auto-discover from git state")
600
764
  .example("$0 run -t checkout", "Run test plans matching 'checkout'")
601
765
  .example("$0 run", "Launch interactive UI")
602
- .example("$0 init", "Create .bangonit/config.toml")
603
- .example("$0 init-ci", "Set up GitHub Actions")
766
+ .example("$0 init", "Set up config, test plans, and CI")
604
767
  .strict()
605
768
  .help()
606
769
  .alias("h", "help")