crewswarm 0.9.2 → 0.9.3
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.
- package/README.md +22 -9
- package/apps/dashboard/dist/assets/{chat-core-Cx4sTxDd.js → chat-core-3KirthZA.js} +1 -1
- package/apps/dashboard/dist/assets/index-GSWxxEPO.js +2 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
- package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-BselH1c0.js +1 -0
- package/apps/dashboard/dist/index.html +82 -11
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +3 -3
- package/crew-lead.mjs +34 -4
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/crew-lead/chat-handler.mjs +34 -0
- package/lib/crew-lead/http-server.mjs +55 -14
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +7 -0
- package/lib/crew-lead/wave-dispatcher.mjs +15 -3
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/engine-registry.mjs +9 -0
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +5 -2
- package/lib/runtime/paths.mjs +12 -8
- package/package.json +35 -15
- package/scripts/capture-build-flow.mjs +118 -0
- package/scripts/coverage-report.mjs +209 -0
- package/scripts/coverage-summary.mjs +47 -0
- package/scripts/dashboard-validation.mjs +74 -0
- package/scripts/dashboard.mjs +560 -70
- package/scripts/live-bridge-matrix.mjs +79 -0
- package/scripts/live-cli-matrix.mjs +166 -0
- package/scripts/live-crewchat-check.mjs +42 -0
- package/scripts/live-engine-matrix.mjs +50 -0
- package/scripts/live-provider-failover-matrix.mjs +107 -0
- package/scripts/live-provider-matrix.mjs +228 -0
- package/scripts/restart-all-from-repo.sh +4 -4
- package/scripts/smoke-dispatch.mjs +4 -1
- package/scripts/test-blast-radius.mjs +204 -0
- package/scripts/test-report-summary.mjs +88 -0
- package/scripts/test-reporter.mjs +651 -0
- package/scripts/test-rerun.mjs +136 -0
- package/scripts/tmux-bridge +130 -0
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/components-BS9fQjE_.js.br +0 -0
- package/apps/dashboard/dist/assets/core-utils-CmOkXgzi.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
- package/apps/dashboard/dist/assets/orchestration-Ca2DLWN-.js.br +0 -0
- package/apps/dashboard/dist/assets/setup-wizard-CA0Or47w.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-agents-tab-BgpIsjkw.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-comms-tab-kguqTIzD.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-contacts-tab-DiOyMYth.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-engines-tab-BsdZVvU0.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-memory-tab-Cu6u13EQ.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-prompts-tab-DVkUNaJd.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-services-tab-DU_LH3uG.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-spending-tab-DEccQHnt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-chat-tab-BNrd88-r.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-swarm-tab-B1AcjL1W.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-usage-tab-BIOOnB-Y.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-waves-tab-SaJDkb4x.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-workflows-tab-B-soSy1k.js.br +0 -0
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/dashboard/index.html +0 -6529
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2828
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -861
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- 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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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); });
|