#!/usr/bin/env npx tsx /** * Presentation Generator * * Reads YAML slide data and generates: * 1. Mermaid sequence diagrams (.mmd) for sequence slides * 2. JSON for Reveal.js slides * * Usage: npx tsx scripts/generate.ts data/agile-agent-team.yaml */ import * as fs from 'fs'; import * as path from 'path'; import * as yaml from 'yaml'; interface Actor { id: string; label: string; description: string; color: string; } interface Step { id: string; from: string; to: string; action: string; label: string; note?: string; callout?: string; danger?: boolean; warning?: boolean; success?: boolean; } interface TitleSlide { type: 'title'; id: string; } interface HookSlide { type: 'hook'; id: string; line: string; } interface ProblemSlide { type: 'problem'; id: string; title: string; points: string[]; } interface SectionTitleSlide { type: 'section-title'; id: string; title: string; subtitle: string; description: string; } interface SequenceSlide { type: 'sequence'; id: string; title: string; actors: string[]; steps: Step[]; } interface InsightSlide { type: 'insight'; id: string; title: string; points: string[]; } interface VisionSlide { type: 'vision'; id: string; title: string; points: string[]; tagline?: string; } type Slide = TitleSlide | HookSlide | ProblemSlide | SectionTitleSlide | SequenceSlide | InsightSlide | VisionSlide; interface PresentationData { meta: { id: string; title: string; subtitle: string; version: string; }; actors: Record; slides: Slide[]; annotations: Record; } // Generate Mermaid sequence diagram for a sequence slide function generateMermaid(data: PresentationData, sequence: SequenceSlide): string { const lines: string[] = []; lines.push('sequenceDiagram'); lines.push(` %% ${sequence.title}`); lines.push(''); // Collect unique actors used in this sequence const usedActors = new Set(); for (const step of sequence.steps) { usedActors.add(step.from); usedActors.add(step.to); } // Add participant declarations for (const actorKey of usedActors) { const actor = data.actors[actorKey]; if (actor) { lines.push(` participant ${actor.id} as ${actor.label}`); } } lines.push(''); // Add steps for (const step of sequence.steps) { const fromActor = data.actors[step.from]; const toActor = data.actors[step.to]; if (!fromActor || !toActor) continue; // Determine arrow style let arrow = '->>'; if (step.action === 'response') { arrow = '-->>'; } // Add the message const label = step.label.replace(/"/g, "'"); lines.push(` ${fromActor.id}${arrow}${toActor.id}: ${label}`); // Add note if present if (step.note) { const noteText = step.note.replace(/"/g, "'"); const position = step.from === step.to ? 'over' : 'right of'; const noteActor = step.from === step.to ? fromActor.id : toActor.id; if (step.danger) { lines.push(` Note ${position} ${noteActor}: ⚠️ ${noteText}`); } else if (step.success) { lines.push(` Note ${position} ${noteActor}: ✓ ${noteText}`); } else { lines.push(` Note ${position} ${noteActor}: ${noteText}`); } } lines.push(''); } return lines.join('\n'); } // Generate JSON for Reveal.js - pass through slides with actor resolution function generateRevealJson(data: PresentationData): object { return { meta: data.meta, actors: data.actors, annotations: data.annotations, slides: data.slides, }; } // Main function main() { const args = process.argv.slice(2); if (args.length < 1) { console.error('Usage: npx tsx scripts/generate.ts '); process.exit(1); } const inputPath = path.resolve(args[0]); const inputDir = path.dirname(inputPath); const baseName = path.basename(inputPath, '.yaml'); // Read and parse YAML let data: PresentationData; try { const yamlContent = fs.readFileSync(inputPath, 'utf-8'); data = yaml.parse(yamlContent) as PresentationData; } catch (error) { const message = error instanceof Error ? error.message : String(error); console.error(`Failed to read/parse ${inputPath}: ${message}`); process.exit(1); } // Validate required fields if (!data.meta || !data.actors || !data.slides) { console.error('Invalid presentation data: missing required fields (meta, actors, slides)'); process.exit(1); } // Output directory const outputDir = path.resolve(inputDir, '..', 'generated'); fs.mkdirSync(outputDir, { recursive: true }); // Find all sequence slides const sequenceSlides = data.slides.filter((s): s is SequenceSlide => s.type === 'sequence'); try { // Generate Mermaid for each sequence for (const sequence of sequenceSlides) { const mermaid = generateMermaid(data, sequence); const mermaidPath = path.join(outputDir, `${baseName}-${sequence.id}.mmd`); fs.writeFileSync(mermaidPath, mermaid); console.log(`Generated: ${mermaidPath}`); } // Generate combined Mermaid if (sequenceSlides.length > 0) { const allMermaid = sequenceSlides .map(seq => generateMermaid(data, seq)) .join('\n\n---\n\n'); const combinedMermaidPath = path.join(outputDir, `${baseName}.mmd`); fs.writeFileSync(combinedMermaidPath, allMermaid); console.log(`Generated: ${combinedMermaidPath}`); } // Generate JSON for Reveal.js const revealJson = generateRevealJson(data); const jsonPath = path.join(outputDir, `${baseName}.json`); fs.writeFileSync(jsonPath, JSON.stringify(revealJson, null, 2)); console.log(`Generated: ${jsonPath}`); } catch (error) { const message = error instanceof Error ? error.message : String(error); console.error(`Failed to write output files: ${message}`); process.exit(1); } } main();