crewswarm 0.9.2 → 0.9.4

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 (228) hide show
  1. package/README.md +22 -9
  2. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
  3. package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
  4. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
  5. package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
  6. package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
  7. package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
  8. package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
  9. package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
  10. package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
  11. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
  12. package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
  13. package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
  14. package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
  15. package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
  16. package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
  17. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
  18. package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
  19. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
  20. package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
  21. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
  22. package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
  23. package/apps/dashboard/dist/index.html +135 -15
  24. package/apps/dashboard/dist/index.html.br +0 -0
  25. package/apps/dashboard/dist/index.html.gz +0 -0
  26. package/apps/vibe/README.md +2 -2
  27. package/apps/vibe/package.json +1 -1
  28. package/apps/vibe/server.mjs +101 -56
  29. package/crew-lead.mjs +34 -4
  30. package/lib/bridges/cli-executor.mjs +1 -1
  31. package/lib/bridges/gateway-ws.mjs +4 -0
  32. package/lib/browser/passthrough-stderr.js +1 -0
  33. package/lib/chat/project-messages.mjs +3 -5
  34. package/lib/cli-process-tracker.mjs +3 -2
  35. package/lib/contacts/identity-linker.mjs +1 -0
  36. package/lib/crew-judge/judge.mjs +19 -18
  37. package/lib/crew-lead/agent-manager.mjs +1 -1
  38. package/lib/crew-lead/background.mjs +14 -1
  39. package/lib/crew-lead/chat-handler.mjs +38 -1
  40. package/lib/crew-lead/http-server.mjs +106 -57
  41. package/lib/crew-lead/llm-caller.mjs +24 -8
  42. package/lib/crew-lead/prompts.mjs +14 -1
  43. package/lib/crew-lead/tools.mjs +3 -2
  44. package/lib/crew-lead/wave-dispatcher.mjs +19 -5
  45. package/lib/crew-lead/ws-router.mjs +219 -27
  46. package/lib/engines/crew-cli.mjs +1 -1
  47. package/lib/engines/engine-registry.mjs +14 -3
  48. package/lib/engines/rt-envelope.mjs +1 -0
  49. package/lib/engines/runners.mjs +28 -4
  50. package/lib/gemini-cli-passthrough-noise.mjs +1 -1
  51. package/lib/integrations/code-search.mjs +4 -3
  52. package/lib/memory/shared-adapter.mjs +23 -10
  53. package/lib/pipeline/manager.mjs +2 -1
  54. package/lib/runtime/config.mjs +1 -1
  55. package/lib/runtime/paths.mjs +12 -8
  56. package/lib/runtime/spending.mjs +2 -1
  57. package/package.json +42 -14
  58. package/scripts/capture-build-flow.mjs +118 -0
  59. package/scripts/coverage-report.mjs +209 -0
  60. package/scripts/coverage-summary.mjs +47 -0
  61. package/scripts/dashboard-validation.mjs +76 -0
  62. package/scripts/dashboard.mjs +1667 -551
  63. package/scripts/generate-openapi.mjs +683 -277
  64. package/scripts/live-bridge-matrix.mjs +79 -0
  65. package/scripts/live-cli-matrix.mjs +166 -0
  66. package/scripts/live-crewchat-check.mjs +42 -0
  67. package/scripts/live-engine-matrix.mjs +50 -0
  68. package/scripts/live-provider-failover-matrix.mjs +107 -0
  69. package/scripts/live-provider-matrix.mjs +228 -0
  70. package/scripts/restart-all-from-repo.sh +4 -4
  71. package/scripts/restart-service.sh +12 -9
  72. package/scripts/smoke-dispatch.mjs +4 -1
  73. package/scripts/test-blast-radius.mjs +204 -0
  74. package/scripts/test-report-summary.mjs +88 -0
  75. package/scripts/test-reporter.mjs +651 -0
  76. package/scripts/test-rerun.mjs +136 -0
  77. package/scripts/tmux-bridge +130 -0
  78. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js +0 -1
  79. package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
  80. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
  81. package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
  82. package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
  83. package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
  84. package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
  85. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
  86. package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
  87. package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
  88. package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
  89. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
  90. package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
  91. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
  92. package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
  93. package/apps/dashboard/index.html +0 -6529
  94. package/apps/dashboard/package.json +0 -15
  95. package/apps/dashboard/src/app.js +0 -2828
  96. package/apps/dashboard/src/app.js.br +0 -0
  97. package/apps/dashboard/src/app.js.gz +0 -0
  98. package/apps/dashboard/src/chat/chat-actions.js +0 -1847
  99. package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
  100. package/apps/dashboard/src/chat/unified-messages.js +0 -327
  101. package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
  102. package/apps/dashboard/src/cli-process.js +0 -208
  103. package/apps/dashboard/src/cli-process.js.br +0 -0
  104. package/apps/dashboard/src/cli-process.js.gz +0 -0
  105. package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
  106. package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
  107. package/apps/dashboard/src/core/api.js +0 -18
  108. package/apps/dashboard/src/core/api.js.br +0 -0
  109. package/apps/dashboard/src/core/dom.js +0 -228
  110. package/apps/dashboard/src/core/dom.js.br +0 -0
  111. package/apps/dashboard/src/core/state.js +0 -91
  112. package/apps/dashboard/src/core/state.js.br +0 -0
  113. package/apps/dashboard/src/core/task-manager.js +0 -134
  114. package/apps/dashboard/src/core/task-manager.js.br +0 -0
  115. package/apps/dashboard/src/orchestration-status.js +0 -127
  116. package/apps/dashboard/src/orchestration-status.js.br +0 -0
  117. package/apps/dashboard/src/setup-wizard.js +0 -562
  118. package/apps/dashboard/src/setup-wizard.js.br +0 -0
  119. package/apps/dashboard/src/styles.css +0 -2085
  120. package/apps/dashboard/src/styles.css.br +0 -0
  121. package/apps/dashboard/src/styles.css.gz +0 -0
  122. package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
  123. package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
  124. package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
  125. package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
  126. package/apps/dashboard/src/tabs/comms-tab.js +0 -955
  127. package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
  128. package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
  129. package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
  130. package/apps/dashboard/src/tabs/engines-tab.js +0 -175
  131. package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
  132. package/apps/dashboard/src/tabs/memory-tab.js +0 -182
  133. package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
  134. package/apps/dashboard/src/tabs/models-tab.js +0 -450
  135. package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
  136. package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
  137. package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
  138. package/apps/dashboard/src/tabs/projects-tab.js +0 -663
  139. package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
  140. package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
  141. package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
  142. package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
  143. package/apps/dashboard/src/tabs/services-tab.js +0 -202
  144. package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
  145. package/apps/dashboard/src/tabs/settings-tab.js +0 -861
  146. package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
  147. package/apps/dashboard/src/tabs/skills-tab.js +0 -284
  148. package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
  149. package/apps/dashboard/src/tabs/spending-tab.js +0 -173
  150. package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
  151. package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
  152. package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
  153. package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
  154. package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
  155. package/apps/dashboard/src/tabs/usage-tab.js +0 -390
  156. package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
  157. package/apps/dashboard/src/tabs/waves-tab.js +0 -238
  158. package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
  159. package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
  160. package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
  161. package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
  162. package/apps/vibe/.crew/cost.json +0 -17
  163. package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
  164. package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
  165. package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
  166. package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
  167. package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
  168. package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
  169. package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
  170. package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
  171. package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
  172. package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
  173. package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
  174. package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
  175. package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
  176. package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
  177. package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
  178. package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
  179. package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
  180. package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
  181. package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
  182. package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
  183. package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
  184. package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
  185. package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
  186. package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
  187. package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
  188. package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
  189. package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
  190. package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
  191. package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
  192. package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
  193. package/apps/vibe/.crew/sandbox.json +0 -7
  194. package/apps/vibe/.crew/session.json +0 -330
  195. package/apps/vibe/.crew/training-data.jsonl +0 -0
  196. package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
  197. package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
  198. package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
  199. package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
  200. package/apps/vibe/ARCHITECTURE.md +0 -3393
  201. package/apps/vibe/QUICK-REFERENCE.md +0 -211
  202. package/apps/vibe/ROADMAP.md +0 -41
  203. package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
  204. package/apps/vibe/VISUAL-GUIDE.md +0 -378
  205. package/apps/vibe/capture-demo.mjs +0 -160
  206. package/apps/vibe/capture-full-demo.mjs +0 -255
  207. package/apps/vibe/capture-quickstart.mjs +0 -256
  208. package/apps/vibe/capture-vibe-assets.mjs +0 -71
  209. package/apps/vibe/capture-vibe-video.mjs +0 -260
  210. package/apps/vibe/check-buttons.js +0 -41
  211. package/apps/vibe/diagnose.html +0 -106
  212. package/apps/vibe/fix-buttons.js +0 -103
  213. package/apps/vibe/index.html +0 -3404
  214. package/apps/vibe/package-lock.json +0 -920
  215. package/apps/vibe/scripts/studio-pty-host.py +0 -117
  216. package/apps/vibe/src/main.js +0 -2940
  217. package/apps/vibe/src/register-all-languages.js +0 -98
  218. package/apps/vibe/start-studio.sh +0 -11
  219. package/apps/vibe/test/accessibility-tests.js +0 -77
  220. package/apps/vibe/test/browser-performance-audit.mjs +0 -205
  221. package/apps/vibe/test/performance-tests.js +0 -120
  222. package/apps/vibe/test/security-tests.js +0 -213
  223. package/apps/vibe/tests/e2e.local.mjs +0 -54
  224. package/apps/vibe/tests/server.smoke.mjs +0 -106
  225. package/apps/vibe/update_website.mjs +0 -74
  226. package/apps/vibe/vite.config.js +0 -19
  227. package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
  228. package/lib/engines/rt-envelope.mjs.backup-current +0 -870
@@ -1,160 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * CrewSwarm Demo Video — Real UI Recording
4
- *
5
- * Records a ~45s demo: Vibe IDE chat + Dashboard tour
6
- * Outputs mp4 + webm via ffmpeg.
7
- *
8
- * Requires: dashboard on :4319, vibe on :3333, playwright, ffmpeg
9
- */
10
- import { chromium } from "playwright";
11
- import { mkdirSync, rmSync, existsSync, statSync, renameSync, readdirSync } from "node:fs";
12
- import { join, dirname } from "node:path";
13
- import { fileURLToPath } from "node:url";
14
- import { execSync } from "node:child_process";
15
-
16
- const __dirname = dirname(fileURLToPath(import.meta.url));
17
- const OUT = join(__dirname, "..", "..", "website", "vibe-assets");
18
- const FINAL_MP4 = join(OUT, "vibe-demo.mp4");
19
- const FINAL_WEBM = join(OUT, "vibe-demo.webm");
20
-
21
- const DASHBOARD_URL = "http://127.0.0.1:4319";
22
- const VIBE_URL = "http://127.0.0.1:3333";
23
- const VIEWPORT = { width: 1440, height: 900 };
24
-
25
- mkdirSync(OUT, { recursive: true });
26
-
27
- async function record() {
28
- console.log("⏳ Checking services...");
29
-
30
- const browser = await chromium.launch({ headless: true });
31
- const context = await browser.newContext({
32
- recordVideo: { dir: OUT, size: VIEWPORT },
33
- viewport: VIEWPORT,
34
- colorScheme: "dark",
35
- });
36
- const page = await context.newPage();
37
-
38
- try {
39
- // ─── SCENE 1: Vibe IDE ──────────────────────────
40
- console.log("🎬 Scene 1: Vibe IDE");
41
- await page.goto(VIBE_URL, { waitUntil: "load", timeout: 30_000 });
42
- await page.waitForTimeout(3_000);
43
-
44
- // Select Studio Workspace project
45
- console.log(" → Selecting project...");
46
- try {
47
- const selects = page.locator("select");
48
- const count = await selects.count();
49
- if (count > 0) {
50
- await selects.first().selectOption({ label: "Studio Workspace" });
51
- await page.waitForTimeout(2_000);
52
- }
53
- } catch (e) { console.log(" (skip project select:", e.message, ")"); }
54
-
55
- // Click a file in the explorer
56
- console.log(" → Opening file...");
57
- try {
58
- // Click on server.mjs in the file tree
59
- await page.locator("text=server.mjs").first().click({ timeout: 3000 });
60
- await page.waitForTimeout(3_000);
61
- } catch { console.log(" (no file to click)"); }
62
-
63
- // Type a message in chat
64
- console.log(" → Typing in chat...");
65
- try {
66
- const textarea = page.locator("textarea").first();
67
- await textarea.click();
68
- const msg = "Add a GET /health endpoint that returns { status: \"ok\" }";
69
- for (const ch of msg) {
70
- await page.keyboard.type(ch, { delay: 30 });
71
- }
72
- await page.waitForTimeout(500);
73
- console.log(" → Sending message...");
74
- await page.keyboard.press("Enter");
75
- // Wait for agent response to stream
76
- console.log(" → Waiting for response (20s)...");
77
- await page.waitForTimeout(20_000);
78
- } catch (e) { console.log(" (chat error:", e.message, ")"); }
79
-
80
- // Show terminal
81
- console.log(" → Toggling terminal...");
82
- try {
83
- await page.locator("button", { hasText: "Terminal" }).click({ timeout: 2000 });
84
- await page.waitForTimeout(2_000);
85
- } catch { console.log(" (no terminal button)"); }
86
-
87
- // Hold for a beat
88
- await page.waitForTimeout(2_000);
89
-
90
- // ─── SCENE 2: Dashboard ─────────────────────────
91
- console.log("🎬 Scene 2: Dashboard");
92
- await page.goto(DASHBOARD_URL, { waitUntil: "load", timeout: 60_000 });
93
- await page.waitForTimeout(4_000);
94
-
95
- // Click through sidebar tabs
96
- const tabs = ["Sessions", "Agents", "Models", "Engines", "Swarm"];
97
- for (const name of tabs) {
98
- try {
99
- console.log(` → ${name}...`);
100
- await page.locator(`text="${name}"`).first().click({ timeout: 2000 });
101
- await page.waitForTimeout(3_500);
102
- } catch { console.log(` (skip ${name})`); }
103
- }
104
-
105
- // End on Chat
106
- try {
107
- await page.locator('text="Chat"').first().click({ timeout: 2000 });
108
- await page.waitForTimeout(3_000);
109
- } catch {}
110
-
111
- } finally {
112
- console.log("💾 Saving...");
113
- await page.close();
114
- await context.close();
115
- await browser.close();
116
- }
117
-
118
- // Find the recorded webm (Playwright names it randomly)
119
- const webms = readdirSync(OUT).filter(f => f.endsWith(".webm") && f !== "vibe-demo.webm");
120
- if (webms.length === 0) {
121
- console.error("❌ No recording found");
122
- process.exit(1);
123
- }
124
- // Use the newest one
125
- webms.sort((a, b) => statSync(join(OUT, b)).mtimeMs - statSync(join(OUT, a)).mtimeMs);
126
- const rawPath = join(OUT, webms[0]);
127
- console.log(`📼 Raw recording: ${rawPath}`);
128
-
129
- // Convert to mp4
130
- console.log("🎞️ Converting to mp4...");
131
- rmSync(FINAL_MP4, { force: true });
132
- execSync(
133
- `ffmpeg -y -i "${rawPath}" -vcodec libx264 -crf 23 -preset slow -vf "scale=1440:900:flags=lanczos" -movflags +faststart -an "${FINAL_MP4}"`,
134
- { stdio: "inherit" }
135
- );
136
-
137
- // Convert to webm (for web embed)
138
- console.log("🎞️ Converting to webm...");
139
- rmSync(FINAL_WEBM, { force: true });
140
- execSync(
141
- `ffmpeg -y -i "${rawPath}" -c:v libvpx-vp9 -crf 30 -b:v 0 -vf "scale=1440:900:flags=lanczos" -an "${FINAL_WEBM}"`,
142
- { stdio: "inherit" }
143
- );
144
-
145
- // Clean up raw
146
- rmSync(rawPath, { force: true });
147
-
148
- const mp4KB = Math.round(statSync(FINAL_MP4).size / 1024);
149
- const webmKB = Math.round(statSync(FINAL_WEBM).size / 1024);
150
- console.log(`\n✨ Done:`);
151
- console.log(` mp4: ${FINAL_MP4} (${mp4KB} KB)`);
152
- console.log(` webm: ${FINAL_WEBM} (${webmKB} KB)`);
153
- }
154
-
155
- record()
156
- .then(() => console.log("\n🎬 Demo capture complete!"))
157
- .catch((err) => {
158
- console.error("❌ Failed:", err.message);
159
- process.exit(1);
160
- });
@@ -1,255 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * CrewSwarm Full Demo — Screenshot Capture + Slideshow Video
4
- *
5
- * Takes screenshots of all key features across Vibe + Dashboard,
6
- * then stitches them into a crossfade slideshow video.
7
- *
8
- * Requires: vibe on :3333, dashboard on :4319, playwright, ffmpeg
9
- */
10
- import { chromium } from "playwright";
11
- import { mkdirSync, rmSync, existsSync, statSync, readdirSync } from "node:fs";
12
- import { join, dirname } from "node:path";
13
- import { fileURLToPath } from "node:url";
14
- import { execSync } from "node:child_process";
15
-
16
- const __dirname = dirname(fileURLToPath(import.meta.url));
17
- const OUT = join(__dirname, "..", "..", "website", "vibe-assets");
18
- const FINAL_MP4 = join(OUT, "vibe-demo.mp4");
19
- const FINAL_WEBM = join(OUT, "vibe-demo.webm");
20
-
21
- const VIBE_URL = "http://127.0.0.1:3333";
22
- const DASHBOARD_URL = "http://127.0.0.1:4319";
23
- const VIEWPORT = { width: 1440, height: 900 };
24
-
25
- // Slide duration in seconds for the final video
26
- const SLIDE_DURATION = 3.5;
27
- const CROSSFADE_DURATION = 0.8;
28
-
29
- mkdirSync(OUT, { recursive: true });
30
-
31
- // Clean old slides
32
- for (const f of readdirSync(OUT)) {
33
- if (f.startsWith("slide-") && f.endsWith(".png")) {
34
- rmSync(join(OUT, f), { force: true });
35
- }
36
- }
37
-
38
- let slideNum = 0;
39
- function slidePath() {
40
- slideNum++;
41
- return join(OUT, `slide-${String(slideNum).padStart(2, "0")}.png`);
42
- }
43
-
44
- async function capture() {
45
- console.log("Checking services...");
46
-
47
- const browser = await chromium.launch({ headless: true });
48
-
49
- // ─── VIBE SCREENSHOTS ─────────────────────────────────
50
- console.log("\n=== VIBE IDE ===");
51
- const vibeCtx = await browser.newContext({
52
- viewport: VIEWPORT,
53
- colorScheme: "dark",
54
- });
55
- const vibe = await vibeCtx.newPage();
56
-
57
- // Slide 1: Vibe IDE — editor view
58
- console.log(" [1] Vibe IDE — editor overview");
59
- await vibe.goto(VIBE_URL, { waitUntil: "load", timeout: 30_000 });
60
- await vibe.waitForTimeout(3_000);
61
- // Try to open a file
62
- try {
63
- await vibe.locator("text=server.mjs").first().click({ timeout: 3000 });
64
- await vibe.waitForTimeout(2_000);
65
- } catch { console.log(" (no file to open)"); }
66
- await vibe.screenshot({ path: slidePath() });
67
-
68
- // Slide 2: Vibe — Claude Code selected
69
- console.log(" [2] Vibe — Claude Code engine");
70
- try {
71
- const sel = vibe.locator("#chat-mode-selector");
72
- await sel.selectOption("cli:claude");
73
- await vibe.waitForTimeout(1_000);
74
- } catch { console.log(" (skip engine select)"); }
75
- await vibe.screenshot({ path: slidePath() });
76
-
77
- // Slide 3: Vibe — Cursor CLI selected
78
- console.log(" [3] Vibe — Cursor CLI engine");
79
- try {
80
- const sel = vibe.locator("#chat-mode-selector");
81
- await sel.selectOption("cli:cursor");
82
- await vibe.waitForTimeout(1_000);
83
- } catch { console.log(" (skip engine select)"); }
84
- await vibe.screenshot({ path: slidePath() });
85
-
86
- // Slide 4: Vibe — Gemini CLI selected
87
- console.log(" [4] Vibe — Gemini CLI engine");
88
- try {
89
- const sel = vibe.locator("#chat-mode-selector");
90
- await sel.selectOption("cli:gemini");
91
- await vibe.waitForTimeout(1_000);
92
- } catch { console.log(" (skip engine select)"); }
93
- await vibe.screenshot({ path: slidePath() });
94
-
95
- // Slide 5: Vibe — typing a prompt in the chat input
96
- console.log(" [5] Vibe — typing prompt");
97
- try {
98
- const sel = vibe.locator("#chat-mode-selector");
99
- await sel.selectOption("cli:claude");
100
- await vibe.waitForTimeout(500);
101
- const chatInput = vibe.locator("#chat-input");
102
- await chatInput.click({ timeout: 5000 });
103
- const msg = "Add session resume for all CLI engines with --resume flag";
104
- await chatInput.fill(msg);
105
- await vibe.waitForTimeout(500);
106
- } catch (e) { console.log(" (skip prompt:", e.message, ")"); }
107
- await vibe.screenshot({ path: slidePath() });
108
-
109
- // Slide 6: Vibe — send and capture response
110
- console.log(" [6] Vibe — agent working");
111
- try {
112
- await vibe.keyboard.press("Enter");
113
- // Wait for response to stream
114
- await vibe.waitForTimeout(18_000);
115
- } catch { console.log(" (skip response)"); }
116
- await vibe.screenshot({ path: slidePath() });
117
-
118
- // Slide 7: Vibe — terminal view
119
- console.log(" [7] Vibe — terminal");
120
- try {
121
- await vibe.locator("button", { hasText: "Terminal" }).click({ timeout: 2000 });
122
- await vibe.waitForTimeout(2_000);
123
- } catch { console.log(" (no terminal)"); }
124
- await vibe.screenshot({ path: slidePath() });
125
-
126
- await vibe.close();
127
- await vibeCtx.close();
128
-
129
- // ─── DASHBOARD SCREENSHOTS ────────────────────────────
130
- console.log("\n=== DASHBOARD ===");
131
- const dashCtx = await browser.newContext({
132
- viewport: VIEWPORT,
133
- colorScheme: "dark",
134
- });
135
- const dash = await dashCtx.newPage();
136
-
137
- await dash.goto(DASHBOARD_URL, { waitUntil: "load", timeout: 60_000 });
138
- await dash.waitForTimeout(4_000);
139
-
140
- // Slide 8: Dashboard — Chat view (default)
141
- console.log(" [8] Dashboard — Chat");
142
- await dash.screenshot({ path: slidePath() });
143
-
144
- // Dashboard tabs to cycle through
145
- const dashTabs = [
146
- { name: "Sessions", label: "9" },
147
- { name: "Agents", label: "10" },
148
- { name: "Engines", label: "11" },
149
- { name: "Models", label: "12" },
150
- { name: "Build", label: "13" },
151
- { name: "Swarm", label: "14" },
152
- ];
153
-
154
- for (const tab of dashTabs) {
155
- console.log(` [${tab.label}] Dashboard — ${tab.name}`);
156
- try {
157
- await dash.locator(`text="${tab.name}"`).first().click({ timeout: 3000 });
158
- await dash.waitForTimeout(2_500);
159
- await dash.screenshot({ path: slidePath() });
160
- } catch {
161
- console.log(` (skip ${tab.name})`);
162
- }
163
- }
164
-
165
- await dash.close();
166
- await dashCtx.close();
167
- await browser.close();
168
-
169
- console.log(`\n${slideNum} screenshots captured.`);
170
- return slideNum;
171
- }
172
-
173
- function buildVideo(totalSlides) {
174
- console.log("\n=== BUILDING VIDEO ===");
175
-
176
- // Build ffmpeg filter for crossfade slideshow
177
- // Each slide shows for SLIDE_DURATION, with CROSSFADE_DURATION transition
178
- const slides = [];
179
- for (let i = 1; i <= totalSlides; i++) {
180
- const p = join(OUT, `slide-${String(i).padStart(2, "0")}.png`);
181
- if (!existsSync(p)) continue;
182
- slides.push(p);
183
- }
184
-
185
- if (slides.length === 0) {
186
- console.error("No slides found!");
187
- process.exit(1);
188
- }
189
-
190
- // Simple approach: use concat demuxer with each image shown for SLIDE_DURATION
191
- // Then add crossfade in a second pass
192
- const totalDuration = slides.length * SLIDE_DURATION;
193
- console.log(` ${slides.length} slides, ${SLIDE_DURATION}s each = ~${Math.round(totalDuration)}s video`);
194
-
195
- // Build input args and filter complex for crossfade
196
- const inputs = slides.map(s => `-loop 1 -t ${SLIDE_DURATION} -i "${s}"`).join(" ");
197
-
198
- let filterComplex = "";
199
- if (slides.length === 1) {
200
- filterComplex = `[0:v]scale=${VIEWPORT.width}:${VIEWPORT.height},format=yuv420p[outv]`;
201
- } else {
202
- // Chain crossfades: [0][1]xfade -> [v01], [v01][2]xfade -> [v012], etc.
203
- const cf = CROSSFADE_DURATION;
204
- let lastLabel = "0:v";
205
- for (let i = 1; i < slides.length; i++) {
206
- const offset = i * SLIDE_DURATION - i * cf;
207
- const outLabel = i === slides.length - 1 ? "outv" : `v${i}`;
208
- filterComplex += `[${lastLabel}][${i}:v]xfade=transition=fade:duration=${cf}:offset=${offset.toFixed(2)}[${outLabel}];\n`;
209
- lastLabel = outLabel;
210
- }
211
- // Remove trailing semicolon+newline if present
212
- filterComplex = filterComplex.replace(/;\n$/, "");
213
- }
214
-
215
- // Generate MP4
216
- console.log(" Converting to mp4...");
217
- rmSync(FINAL_MP4, { force: true });
218
- const mp4Cmd = `ffmpeg -y ${inputs} -filter_complex "${filterComplex}" -map "[outv]" -c:v libx264 -crf 23 -preset slow -pix_fmt yuv420p -movflags +faststart -an "${FINAL_MP4}"`;
219
- try {
220
- execSync(mp4Cmd, { stdio: "pipe", timeout: 120_000 });
221
- } catch (e) {
222
- console.error(" mp4 error:", e.stderr?.toString().slice(-500));
223
- // Fallback: simple concat without crossfade
224
- console.log(" Trying simple concat fallback...");
225
- const concatInputs = slides.map(s => `-loop 1 -t ${SLIDE_DURATION} -i "${s}"`).join(" ");
226
- const concatFilter = slides.map((_, i) => `[${i}:v]scale=${VIEWPORT.width}:${VIEWPORT.height},format=yuv420p,setpts=PTS-STARTPTS[v${i}]`).join(";") +
227
- ";" + slides.map((_, i) => `[v${i}]`).join("") + `concat=n=${slides.length}:v=1:a=0[outv]`;
228
- execSync(`ffmpeg -y ${concatInputs} -filter_complex "${concatFilter}" -map "[outv]" -c:v libx264 -crf 23 -preset slow -movflags +faststart -an "${FINAL_MP4}"`,
229
- { stdio: "pipe", timeout: 120_000 });
230
- }
231
-
232
- // Generate WebM
233
- console.log(" Converting to webm...");
234
- rmSync(FINAL_WEBM, { force: true });
235
- execSync(
236
- `ffmpeg -y -i "${FINAL_MP4}" -c:v libvpx-vp9 -crf 30 -b:v 0 -an "${FINAL_WEBM}"`,
237
- { stdio: "pipe", timeout: 120_000 }
238
- );
239
-
240
- const mp4KB = Math.round(statSync(FINAL_MP4).size / 1024);
241
- const webmKB = Math.round(statSync(FINAL_WEBM).size / 1024);
242
- console.log(`\nDone:`);
243
- console.log(` mp4: ${FINAL_MP4} (${mp4KB} KB)`);
244
- console.log(` webm: ${FINAL_WEBM} (${webmKB} KB)`);
245
- }
246
-
247
- capture()
248
- .then((total) => {
249
- buildVideo(total);
250
- console.log("\nDemo capture complete!");
251
- })
252
- .catch((err) => {
253
- console.error("Failed:", err.message);
254
- process.exit(1);
255
- });
@@ -1,256 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * CrewSwarm Quickstart Video — Simulated Install + First Run
4
- *
5
- * Creates a terminal-style recording showing the install and first use flow.
6
- * Uses Playwright to render HTML terminal frames and capture as screenshots,
7
- * then stitches into a video with ffmpeg.
8
- */
9
- import { chromium } from "playwright";
10
- import { mkdirSync, rmSync, existsSync, statSync, readdirSync, writeFileSync } from "node:fs";
11
- import { join, dirname } from "node:path";
12
- import { fileURLToPath } from "node:url";
13
- import { execSync } from "node:child_process";
14
-
15
- const __dirname = dirname(fileURLToPath(import.meta.url));
16
- const OUT = join(__dirname, "..", "..", "website", "vibe-assets");
17
- const FINAL_MP4 = join(OUT, "quickstart.mp4");
18
- const FINAL_WEBM = join(OUT, "quickstart.webm");
19
- const VIEWPORT = { width: 900, height: 560 };
20
- const SLIDE_DURATION = 3;
21
-
22
- mkdirSync(OUT, { recursive: true });
23
-
24
- // Clean old quickstart slides
25
- for (const f of readdirSync(OUT)) {
26
- if (f.startsWith("qs-") && f.endsWith(".png")) rmSync(join(OUT, f), { force: true });
27
- }
28
-
29
- function terminalHTML(lines, title = "Terminal — crewswarm") {
30
- const content = lines.map(l => {
31
- if (l.startsWith("$")) return `<span style="color:#4ade80;">${esc(l)}</span>`;
32
- if (l.startsWith("//")) return `<span style="color:#6b7280;font-style:italic;">${esc(l)}</span>`;
33
- if (l.includes("✓") || l.includes("✔") || l.includes("Done") || l.includes("ready")) return `<span style="color:#4ade80;">${esc(l)}</span>`;
34
- if (l.includes("⚡") || l.includes("→")) return `<span style="color:#60a5fa;">${esc(l)}</span>`;
35
- if (l.includes("🧠") || l.includes("📐") || l.includes("🔧")) return `<span style="color:#fbbf24;">${esc(l)}</span>`;
36
- return esc(l);
37
- }).join("\n");
38
-
39
- return `<!DOCTYPE html>
40
- <html><head><style>
41
- * { margin:0; padding:0; box-sizing:border-box; }
42
- body { background:#0a0e17; font-family:'SF Mono','Fira Code',monospace; padding:20px; }
43
- .terminal { background:#0d1117; border:1px solid #21262d; border-radius:12px; overflow:hidden; }
44
- .bar { background:#161b22; padding:10px 14px; display:flex; align-items:center; gap:8px; }
45
- .dot { width:12px; height:12px; border-radius:50%; }
46
- .red { background:#ff5f57; } .yellow { background:#febc2e; } .green { background:#28c840; }
47
- .title { color:#8b949e; font-size:13px; margin-left:8px; }
48
- .body { padding:16px 20px; color:#c9d1d9; font-size:14px; line-height:1.7; white-space:pre-wrap; min-height:400px; }
49
- </style></head><body>
50
- <div class="terminal">
51
- <div class="bar">
52
- <div class="dot red"></div><div class="dot yellow"></div><div class="dot green"></div>
53
- <span class="title">${esc(title)}</span>
54
- </div>
55
- <div class="body">${content}</div>
56
- </div>
57
- </body></html>`;
58
- }
59
-
60
- function esc(s) {
61
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
62
- }
63
-
64
- const frames = [
65
- {
66
- title: "Install — real output from Docker",
67
- lines: [
68
- "$ npm install -g crewswarm",
69
- "",
70
- "added 59 packages in 9s",
71
- "",
72
- "$ crewswarm --version",
73
- "0.8.2-beta",
74
- "",
75
- "$ crewswarm",
76
- "",
77
- "⚡ crewswarm v0.8.2-beta",
78
- "→ Dashboard: http://localhost:4319",
79
- "→ Vibe IDE: http://localhost:3333",
80
- "→ crew-lead: http://localhost:5010",
81
- "→ MCP server: http://localhost:5020",
82
- "",
83
- "✓ All services ready",
84
- ],
85
- },
86
- {
87
- title: "Configure — pick your models",
88
- lines: [
89
- "// Open Dashboard → Models tab",
90
- "// Set a model per agent — or use CLI OAuth (no keys needed)",
91
- "",
92
- " crew-lead ......... Groq Llama 3.3 70B Free",
93
- " crew-pm ........... Gemini 2.5 Flash $0.075/M",
94
- " crew-coder ........ Claude Sonnet 4.6 $3/M",
95
- " crew-coder-front .. Cursor (composer-2) Cursor sub",
96
- " crew-qa ........... Gemini CLI (OAuth) Free",
97
- " crew-fixer ........ Codex CLI (OAuth) Codex sub",
98
- "",
99
- "// Or use Claude Code / Cursor / Gemini CLI directly —",
100
- "// just login once with OAuth, no API keys",
101
- "",
102
- "✓ Models configured from dashboard — no config files",
103
- ],
104
- },
105
- {
106
- title: "Build — one sentence, full feature",
107
- lines: [
108
- '$ crew run "Build a REST API with auth, tests, and docs"',
109
- "",
110
- "🧠 crew-pm planning...",
111
- " → Phase MVP: 3 tasks (auth, routes, tests)",
112
- " → Phase 1: 2 tasks (docs, error handling)",
113
- "",
114
- "📐 Wave 1: dispatching to 3 agents in parallel",
115
- " → crew-coder-back: auth middleware + JWT",
116
- " → crew-coder-back: CRUD routes + validation",
117
- " → crew-qa: test suite (jest)",
118
- "",
119
- "✓ Wave 1 complete (2m 14s)",
120
- "",
121
- "📐 Wave 2: dispatching to 2 agents",
122
- " → crew-coder: API docs (OpenAPI spec)",
123
- " → crew-fixer: error handling + edge cases",
124
- ],
125
- },
126
- {
127
- title: "Build — done",
128
- lines: [
129
- "✓ Wave 2 complete (1m 38s)",
130
- "",
131
- "🧠 crew-pm: all phases complete",
132
- "",
133
- " Files created:",
134
- " src/middleware/auth.js ✓",
135
- " src/routes/users.js ✓",
136
- " src/routes/posts.js ✓",
137
- " tests/auth.test.js ✓",
138
- " tests/routes.test.js ✓",
139
- " docs/openapi.yaml ✓",
140
- "",
141
- " Tests: 14 passed, 0 failed",
142
- " Duration: 3m 52s (2 phases, 2 waves)",
143
- "",
144
- "✓ Build complete. Files on disk. Ship it.",
145
- ],
146
- },
147
- {
148
- title: "Rate limited? Switch engines",
149
- lines: [
150
- "// Claude hit rate limit mid-session?",
151
- "// Switch to Cursor or Gemini — session context preserved",
152
- "",
153
- " Dashboard → Engines tab → click any engine",
154
- "",
155
- " Claude Code .... --resume <session-id>",
156
- " Cursor CLI ..... --resume=<chat-id>",
157
- " Gemini CLI ..... --resume <session-id>",
158
- " Codex CLI ...... codex resume <thread-id>",
159
- " OpenCode ....... --continue",
160
- "",
161
- "// Or in Vibe: just pick a different CLI from the dropdown",
162
- "// Your conversation continues where you left off",
163
- "",
164
- "✓ 6 engines. Native session resume. No vendor lock-in.",
165
- ],
166
- },
167
- {
168
- title: "Vibe IDE — code in your browser",
169
- lines: [
170
- "// Open http://localhost:3333",
171
- "",
172
- " ┌─────────────────────────────────────────────┐",
173
- " │ Files │ Monaco Editor │ Chat │",
174
- " │ │ │ │",
175
- " │ src/ │ server.mjs │ Claude Code │",
176
- " │ tests/ │ import http... │ Cursor CLI │",
177
- " │ docs/ │ const app = ... │ Gemini CLI │",
178
- " │ │ │ OpenCode │",
179
- " │ │──────────────────│ Codex CLI │",
180
- " │ │ Terminal │ │",
181
- " │ │ $ npm test │ [Clear] │",
182
- " └─────────────────────────────────────────────┘",
183
- "",
184
- " Editor + terminal + chat + 6 engines. All in one tab.",
185
- ],
186
- },
187
- ];
188
-
189
- async function capture() {
190
- console.log("Capturing quickstart frames...");
191
- const browser = await chromium.launch({ headless: true });
192
- const ctx = await browser.newContext({ viewport: VIEWPORT, colorScheme: "dark" });
193
-
194
- let slideNum = 0;
195
- for (const frame of frames) {
196
- slideNum++;
197
- const page = await ctx.newPage();
198
- const html = terminalHTML(frame.lines, frame.title);
199
- const tmpHtml = join(OUT, `_qs_tmp.html`);
200
- writeFileSync(tmpHtml, html);
201
- await page.goto(`file://${tmpHtml}`, { waitUntil: "load" });
202
- await page.waitForTimeout(500);
203
- const path = join(OUT, `qs-${String(slideNum).padStart(2, "0")}.png`);
204
- await page.screenshot({ path });
205
- console.log(` [${slideNum}] ${frame.title}`);
206
- await page.close();
207
- }
208
-
209
- rmSync(join(OUT, "_qs_tmp.html"), { force: true });
210
- await ctx.close();
211
- await browser.close();
212
- return slideNum;
213
- }
214
-
215
- function buildVideo(totalSlides) {
216
- console.log("\nBuilding quickstart video...");
217
- const slides = [];
218
- for (let i = 1; i <= totalSlides; i++) {
219
- const p = join(OUT, `qs-${String(i).padStart(2, "0")}.png`);
220
- if (existsSync(p)) slides.push(p);
221
- }
222
-
223
- const inputs = slides.map(s => `-loop 1 -t ${SLIDE_DURATION} -i "${s}"`).join(" ");
224
- const cf = 0.6;
225
-
226
- let filterComplex = "";
227
- let lastLabel = "0:v";
228
- for (let i = 1; i < slides.length; i++) {
229
- const offset = i * SLIDE_DURATION - i * cf;
230
- const outLabel = i === slides.length - 1 ? "outv" : `v${i}`;
231
- filterComplex += `[${lastLabel}][${i}:v]xfade=transition=fade:duration=${cf}:offset=${offset.toFixed(2)}[${outLabel}];`;
232
- lastLabel = outLabel;
233
- }
234
- filterComplex = filterComplex.replace(/;$/, "");
235
-
236
- rmSync(FINAL_MP4, { force: true });
237
- rmSync(FINAL_WEBM, { force: true });
238
-
239
- console.log(" → mp4...");
240
- execSync(`ffmpeg -y ${inputs} -filter_complex "${filterComplex}" -map "[outv]" -c:v libx264 -crf 23 -preset slow -pix_fmt yuv420p -movflags +faststart -an "${FINAL_MP4}"`,
241
- { stdio: "pipe", timeout: 60_000 });
242
-
243
- console.log(" → webm...");
244
- execSync(`ffmpeg -y -i "${FINAL_MP4}" -c:v libvpx-vp9 -crf 30 -b:v 0 -an "${FINAL_WEBM}"`,
245
- { stdio: "pipe", timeout: 60_000 });
246
-
247
- const mp4KB = Math.round(statSync(FINAL_MP4).size / 1024);
248
- const webmKB = Math.round(statSync(FINAL_WEBM).size / 1024);
249
- console.log(`\nDone:`);
250
- console.log(` mp4: ${mp4KB} KB`);
251
- console.log(` webm: ${webmKB} KB`);
252
- }
253
-
254
- capture()
255
- .then(n => { buildVideo(n); console.log("\nQuickstart video complete!"); })
256
- .catch(e => { console.error("Failed:", e.message); process.exit(1); });