import { chromium } from 'playwright'; import { join } from 'path'; import { mkdirSync, existsSync, unlinkSync, readdirSync } from 'fs'; const SCREENSHOTS_DIR = './screenshots/cli-demo'; // Ensure directory exists if (!existsSync(SCREENSHOTS_DIR)) { mkdirSync(SCREENSHOTS_DIR, { recursive: true }); } function generateTerminalHTML(prompt, output, status, showCursor) { if (showCursor === undefined) showCursor = true; const cursorHtml = showCursor ? '' : ''; return `
Terminal — skillhub
${prompt}${cursorHtml} ${output}
${status}
`; } // CLI Demo frames - 4 distinct frames const FRAMES = [ // Frame 1: Empty prompt, ready to type { content: generateTerminalHTML('$ ', '', 'Ready'), description: 'Empty prompt' }, // Frame 2: Command typed { content: generateTerminalHTML('$ npx skillhub search pdf', '', 'Press Enter to search...'), description: 'Command typed' }, // Frame 3: Searching animation { content: generateTerminalHTML('$ npx skillhub search pdf', ` ⠋ Searching skills... `, 'Searching...', false), description: 'Searching' }, // Frame 4: Results displayed (final state) { content: generateTerminalHTML('$ npx skillhub search pdf', ` ✔ Found 156 skills: ───────────────────────────────────────────────────────────────── anthropics/skills/pdf ⭐ 30.2k ⬇ 1.2k Read, extract and manipulate PDF files. Convert PDFs... ───────────────────────────────────────────────────────────────── obra/superpowers/pdf-extraction ⭐ 8.1k ⬇ 421 Advanced PDF extraction with OCR support and table... ───────────────────────────────────────────────────────────────── openai/skills/pdf-reader ⭐ 2.2k ⬇ 189 Parse PDF documents and extract structured text... ───────────────────────────────────────────────────────────────── Install with: npx skillhub install <skill-id> Showing 3 of 156. Use --limit to see more. `, 'Done - 156 skills found', false), description: 'Results' }, ]; async function main() { console.log('=== CLI Demo Animation Generator ===\n'); const browser = await chromium.launch({ headless: true }); const context = await browser.newContext({ viewport: { width: 800, height: 500 }, deviceScaleFactor: 2, }); const page = await context.newPage(); // Clean up old frames const existingFiles = readdirSync(SCREENSHOTS_DIR); for (const file of existingFiles) { if (file.endsWith('.png')) { unlinkSync(join(SCREENSHOTS_DIR, file)); } } console.log('Generating 4 distinct frames...'); for (let i = 0; i < FRAMES.length; i++) { const frame = FRAMES[i]; await page.setContent(frame.content); await page.screenshot({ path: join(SCREENSHOTS_DIR, 'frame-' + String(i).padStart(3, '0') + '.png'), }); console.log(' Frame ' + (i + 1) + '/' + FRAMES.length + ': ' + frame.description); } await browser.close(); console.log('\n=== Frames Generated ==='); console.log('\nFrames saved to: ' + SCREENSHOTS_DIR + '/'); console.log('\nTo create GIF:'); console.log(' ffmpeg -framerate 0.5 -i frame-%03d.png -vf "fps=10,scale=800:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 ../cli-demo.gif'); } main().catch(console.error);