← back to AbramsEgo
scripts/demo-video.js
79 lines
#!/usr/bin/env node
// Capture a ~50s click-through tour of the :9773 dashboard (norma-videos pattern:
// per-panel dwell with a red highlight box; muted/no-TTS cut for the landing page).
// Run: NODE_PATH=~/.claude/skills/app-demo-video/node_modules node scripts/demo-video.js
// Output: tmp/demo-raw/<hash>.webm — converted to mp4 by scripts/demo-video.sh
const { chromium } = require('playwright');
const path = require('path');
const BASE = process.env.EGO_URL || 'http://127.0.0.1:9773/';
const OUT_DIR = path.join(__dirname, '..', 'tmp', 'demo-raw');
const W = 1280, H = 720;
// h2 text prefixes, in tour order (matched with startsWith on the emoji+title)
const TOUR = [
'💰 Self-Funding P&L',
'🔌 Cost to run',
'📈 Cost trend',
'🛰 Fleet · Kamatera',
'🖥 Fleet · Local',
'🐤 Canary board',
'🏛 Officers',
'🏆 Wins',
'💬 Live Claude sessions',
'🧠 Model usage',
'🤖 Ask AbramsEgo',
];
const DWELL_MS = 3600; // per-panel hold
const INTRO_MS = 4500; // top-of-page hold
(async () => {
// skill's playwright (1.59.1) expects browser build 1217, which isn't cached;
// point at the cached headless-shell build instead of downloading a new one
const shell = process.env.HOME + '/Library/Caches/ms-playwright/chromium_headless_shell-1228/chrome-headless-shell-mac-arm64/chrome-headless-shell';
const browser = await chromium.launch({ executablePath: shell });
const ctx = await browser.newContext({
viewport: { width: W, height: H },
recordVideo: { dir: OUT_DIR, size: { width: W, height: H } },
httpCredentials: { username: 'admin', password: 'DW2024!' },
});
const page = await ctx.newPage();
await page.goto(BASE, { waitUntil: 'networkidle' }).catch(() => {});
// let the snapshot panels populate
await page.waitForTimeout(3000);
await page.addStyleTag({ content: `
.demo-hl { outline: 3px solid #ff4d4d !important; outline-offset: 4px;
border-radius: 8px; transition: outline-color .3s; }
html { scroll-behavior: smooth; }
`});
await page.waitForTimeout(INTRO_MS);
for (const title of TOUR) {
const hit = await page.evaluate((t) => {
const h = [...document.querySelectorAll('h2')]
.find(el => el.textContent.trim().startsWith(t.slice(0, 6)) && el.textContent.includes(t.slice(2).trim().split(' ')[0]));
if (!h) return false;
const panel = h.closest('section,div.panel,article') || h.parentElement;
document.querySelectorAll('.demo-hl').forEach(e => e.classList.remove('demo-hl'));
panel.classList.add('demo-hl');
panel.scrollIntoView({ behavior: 'smooth', block: 'center' });
return true;
}, title);
if (!hit) console.error('panel not found:', title);
await page.waitForTimeout(DWELL_MS);
}
// close on the top metrics bar
await page.evaluate(() => {
document.querySelectorAll('.demo-hl').forEach(e => e.classList.remove('demo-hl'));
window.scrollTo({ top: 0, behavior: 'smooth' });
});
await page.waitForTimeout(3000);
await ctx.close();
await browser.close();
console.log('webm written to', OUT_DIR);
})();