feat: Self-documenting CLI with init, schema, info commands

- Add `init` command to generate starter YAML templates with documentation
- Add `schema` command for CLI-based schema reference
- Add `schema.json` for IDE autocompletion (VS Code, JetBrains)
- Add `info` command and `--version` flag
- Update README with CLI Reference section
This commit is contained in:
lennyzeltser
2026-01-15 13:32:21 -05:00
parent 6edcbab673
commit 958e3fee82
5 changed files with 838 additions and 28 deletions
+42 -19
View File
@@ -7,6 +7,7 @@ After parsing the annotated conversation data you supply in a YAML file, this to
- [How This Is Useful](#how-this-is-useful)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [CLI Reference](#cli-reference)
- [YAML Schema](#yaml-schema)
- [Output Features](#output-features)
- [Embedding in Websites](#embedding-in-websites)
@@ -75,31 +76,52 @@ Note: This requires [Bun](https://bun.sh) to be installed (for the build step).
## Quick Start
Create a YAML file defining your conversation, then generate the HTML:
Generate a starter template, customize it, then build:
```bash
conversation-replay build examples/london-scam.yaml -o demo.html
```
# Create a documented template
conversation-replay init demo.yaml
Output:
```
Loading examples/london-scam.yaml...
Building demo.html...
Done! Generated demo.html
Title: London Scam - Social Engineering Demo
Scenarios: 1
- London Scam: 17 steps (Matt (compromised account), Rakesh)
```
# Edit demo.yaml to define your conversation, then build
conversation-replay build demo.yaml -o demo.html
```bash
# Open in browser
open demo.html
```
The generated replay is responsive, supports dark mode automatically, and looks nice across various browsers and devices.
To explore an existing example instead:
```bash
conversation-replay build examples/london-scam.yaml -o demo.html
```
## CLI Reference
| Command | Description |
|---------|-------------|
| `build <file.yaml> -o <output.html>` | Generate HTML from a YAML file |
| `validate <file.yaml>` | Check a YAML file for errors |
| `init <file.yaml>` | Create a starter template with inline documentation |
| `schema [section]` | Show schema reference (sections: `meta`, `colors`, `speed`, `steps`) |
| `info` | Show tool name, version, and author |
| `--version` | Show version number |
| `--help` | Show help |
Options for `build`:
- `--theme <theme>` &mdash; Override theme (`chat`, `email`, `slack`, `terminal`, `generic`)
- `--no-header` &mdash; Exclude the header with title and description
For IDE autocompletion, add this to the top of your YAML file:
```yaml
# yaml-language-server: $schema=https://raw.githubusercontent.com/lennyzeltser/conversation-replay/master/schema.json
```
## YAML Schema
Run `conversation-replay schema` for a quick reference, or see below for details.
### Basic Structure
```yaml
@@ -271,8 +293,10 @@ conversation-replay/
│ ├── cli.ts # CLI entry point
│ ├── parser.ts # YAML parsing & validation
│ ├── generator.ts # HTML generation (CSS/JS injection)
│ ├── schema.ts # Template generation & schema utilities
│ └── types.ts # TypeScript definitions
├── examples/ # Demo YAML files
├── schema.json # JSON Schema for IDE autocompletion
└── package.json
```
@@ -282,14 +306,11 @@ conversation-replay/
# Install dependencies
bun install
# Build
# Run CLI during development
bun run src/cli.ts build examples/london-scam.yaml -o demo.html
# Validate without building
bun run src/cli.ts validate examples/london-scam.yaml
# Build with options
bun run src/cli.ts build examples/london-scam.yaml -o demo.html --theme email --no-header
bun run src/cli.ts init my-demo.yaml
bun run src/cli.ts schema
```
---
@@ -303,6 +324,8 @@ bun run src/cli.ts build examples/london-scam.yaml -o demo.html --theme email --
| `src/types.ts` | Schema definitions. Edit this to add new YAML properties. |
| `src/generator.ts` | The core engine. Generates the HTML, CSS, and runtime JavaScript. |
| `src/parser.ts` | Input validation logic. |
| `src/schema.ts` | Template generation, JSON schema, and CLI schema reference. |
| `schema.json` | JSON Schema for IDE autocompletion. Regenerate with `schema --json`. |
### Architecture
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "conversation-replay",
"version": "0.1.13",
"version": "0.1.14",
"description": "Create annotated replays of text conversations",
"main": "dist/cli.js",
"type": "module",
@@ -9,6 +9,7 @@
},
"files": [
"dist",
"schema.json",
"README.md"
],
"scripts": {
+292
View File
@@ -0,0 +1,292 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/lennyzeltser/conversation-replay/schema.json",
"title": "Conversation Replay Demo",
"description": "Schema for conversation-replay YAML configuration files",
"type": "object",
"required": [
"meta",
"scenarios"
],
"properties": {
"meta": {
"type": "object",
"description": "Demo metadata and configuration",
"required": [
"title"
],
"properties": {
"title": {
"type": "string",
"description": "Demo title shown in header",
"minLength": 1
},
"description": {
"type": "string",
"description": "Brief description shown below title"
},
"theme": {
"type": "string",
"enum": [
"chat",
"email",
"slack",
"terminal",
"generic"
],
"description": "Visual theme for message styling",
"default": "chat"
},
"articleUrl": {
"type": "string",
"description": "URL for 'View Article' link in header"
},
"hideHeaderInIframe": {
"type": "boolean",
"description": "Auto-hide header when embedded in iframe",
"default": true
},
"autoAdvance": {
"type": "boolean",
"description": "Auto-play next scenario when current one completes"
},
"annotationLabel": {
"type": "string",
"description": "Label shown above annotation content",
"default": "Behind the Scenes"
},
"timerStyle": {
"type": "string",
"enum": [
"bar",
"circle"
],
"description": "Countdown timer display style",
"default": "circle"
},
"cornerStyle": {
"type": "string",
"enum": [
"rounded",
"straight"
],
"description": "Border radius style for containers and bubbles",
"default": "rounded"
},
"initialBlur": {
"type": "number",
"description": "Blur amount in pixels for play overlay",
"default": 1,
"minimum": 0
},
"colors": {
"type": "object",
"description": "Custom color overrides",
"properties": {
"accent": {
"type": "string",
"description": "Primary accent color (buttons, links)"
},
"pageBg": {
"type": "string",
"description": "Page background color"
},
"canvasBg": {
"type": "string",
"description": "Chat container background"
},
"leftBg": {
"type": "string",
"description": "Left participant bubble background"
},
"leftBorder": {
"type": "string",
"description": "Left participant bubble border"
},
"rightBg": {
"type": "string",
"description": "Right participant bubble background"
},
"rightBorder": {
"type": "string",
"description": "Right participant bubble border"
},
"tabInactiveColor": {
"type": "string",
"description": "Inactive tab text color"
},
"annotationText": {
"type": "string",
"description": "Annotation text color"
},
"annotationBorder": {
"type": "string",
"description": "Annotation accent bar color"
}
}
},
"speed": {
"type": "object",
"description": "Timing configuration for playback",
"properties": {
"minDelay": {
"type": "number",
"description": "Minimum delay between steps (ms)",
"default": 3000
},
"maxDelay": {
"type": "number",
"description": "Maximum delay between steps (ms)",
"default": 8000
},
"msPerWord": {
"type": "number",
"description": "Milliseconds per word for reading time",
"default": 200
},
"annotationMultiplier": {
"type": "number",
"description": "Time multiplier for annotations",
"default": 1.15
},
"upNextDelay": {
"type": "number",
"description": "'Up Next' display duration (ms)",
"default": 2500
}
}
}
}
},
"scenarios": {
"type": "array",
"description": "One or more conversation scenarios (shown as tabs if multiple)",
"minItems": 1,
"items": {
"type": "object",
"required": [
"id",
"title",
"participants",
"steps"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this scenario",
"minLength": 1
},
"title": {
"type": "string",
"description": "Display title (shown in tab)",
"minLength": 1
},
"participants": {
"type": "array",
"description": "People/entities in this conversation",
"minItems": 1,
"items": {
"type": "object",
"required": [
"id",
"label"
],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier (referenced in message 'from' field)"
},
"label": {
"type": "string",
"description": "Display name shown above messages"
},
"role": {
"type": "string",
"enum": [
"left",
"right"
],
"description": "Which side messages appear on",
"default": "left"
}
}
}
},
"steps": {
"type": "array",
"description": "Sequence of messages, annotations, and transitions",
"minItems": 1,
"items": {
"oneOf": [
{
"type": "object",
"description": "A message from a participant",
"required": [
"type",
"from",
"content"
],
"properties": {
"type": {
"const": "message"
},
"from": {
"type": "string",
"description": "Participant ID"
},
"content": {
"type": "string",
"description": "Message text"
},
"codeBlock": {
"type": "string",
"description": "Optional code/preformatted text"
},
"footnote": {
"type": "string",
"description": "Optional italic footnote"
}
}
},
{
"type": "object",
"description": "An educational annotation/callout",
"required": [
"type",
"content"
],
"properties": {
"type": {
"const": "annotation"
},
"content": {
"type": "string",
"description": "Annotation text"
}
}
},
{
"type": "object",
"description": "A scene break/transition",
"required": [
"type",
"content"
],
"properties": {
"type": {
"const": "transition"
},
"content": {
"type": "string",
"description": "Transition text (e.g., 'Later that day...')"
}
}
}
]
}
}
}
}
}
}
}
+88 -8
View File
@@ -5,12 +5,16 @@
* Usage:
* conversation-replay build <scenario.yaml> -o <output.html>
* conversation-replay validate <scenario.yaml>
* conversation-replay init <output.yaml>
* conversation-replay schema [section]
*/
import { parseArgs } from 'util';
import { loadDemo, ParseError } from './parser';
import { buildDemo } from './generator';
import { generateTemplate, getSchemaReference, jsonSchema } from './schema';
import type { Theme } from './types';
import packageJson from '../package.json';
const HELP = `
conversation-replay - Create annotated replays of text conversations from YAML
@@ -18,22 +22,31 @@ conversation-replay - Create annotated replays of text conversations from YAML
Usage:
conversation-replay build <scenario.yaml> -o <output.html> [options]
conversation-replay validate <scenario.yaml>
conversation-replay --help
conversation-replay init <output.yaml> [--theme <theme>]
conversation-replay schema [section] [--json]
conversation-replay info
conversation-replay --help | --version
Commands:
build Generate HTML from a scenario file
validate Check a scenario file for errors
init Create a starter YAML template with documentation
schema Show schema reference (sections: meta, colors, speed, steps)
info Show tool name, version, author, and links
Options:
-o, --output <path> Output HTML file path (required for build)
--theme <theme> Override theme (chat, email, slack, terminal, generic)
--no-header Exclude the demo header
--json Output JSON schema (for schema command)
-h, --help Show this help message
-v, --version Show version number
Examples:
conversation-replay build demo.yaml -o demo.html
conversation-replay build demo.yaml -o demo.html --theme email
conversation-replay validate demo.yaml
conversation-replay init my-demo.yaml
conversation-replay schema meta
conversation-replay info
`;
const VALID_THEMES = ['chat', 'email', 'slack', 'terminal', 'generic'];
@@ -45,21 +58,39 @@ async function main() {
output: { type: 'string', short: 'o' },
theme: { type: 'string' },
'no-header': { type: 'boolean' },
json: { type: 'boolean' },
help: { type: 'boolean', short: 'h' },
version: { type: 'boolean', short: 'v' },
},
allowPositionals: true,
});
if (values.version) {
console.log(`conversation-replay v${packageJson.version}`);
process.exit(0);
}
if (values.help || positionals.length === 0) {
console.log(HELP);
process.exit(0);
}
const command = positionals[0];
const inputFile = positionals[1];
const arg1 = positionals[1];
if (!inputFile) {
console.error('Error: No input file specified\n');
// Commands that don't require an input file
if (command === 'schema') {
handleSchema(arg1, values.json);
process.exit(0);
}
if (command === 'info') {
handleInfo();
process.exit(0);
}
if (!arg1) {
console.error('Error: No file specified\n');
console.log(HELP);
process.exit(1);
}
@@ -74,11 +105,15 @@ async function main() {
try {
switch (command) {
case 'build':
await handleBuild(inputFile, values);
await handleBuild(arg1, values);
break;
case 'validate':
await handleValidate(inputFile);
await handleValidate(arg1);
break;
case 'init':
await handleInit(arg1, values.theme as Theme | undefined);
break;
default:
@@ -159,6 +194,51 @@ async function handleValidate(inputFile: string) {
}
}
async function handleInit(outputFile: string, theme?: Theme) {
const fs = await import('fs/promises');
// Check if file exists
try {
await fs.access(outputFile);
console.error(`Error: File already exists: ${outputFile}`);
console.error('Use a different filename or delete the existing file.');
process.exit(1);
} catch {
// File doesn't exist, good to proceed
}
const template = generateTemplate(theme);
await fs.writeFile(outputFile, template, 'utf-8');
console.log(`Created ${outputFile}`);
console.log('');
console.log('Next steps:');
console.log(` 1. Edit ${outputFile} to define your conversation`);
console.log(` 2. Validate: conversation-replay validate ${outputFile}`);
console.log(` 3. Build: conversation-replay build ${outputFile} -o output.html`);
console.log('');
console.log('For schema reference: conversation-replay schema');
}
function handleInfo() {
console.log(`
Conversation Replay v${packageJson.version}
https://github.com/lennyzeltser/conversation-replay
Created by Lenny Zeltser
https://zeltser.com
`.trim());
}
function handleSchema(section?: string, outputJson?: boolean) {
if (outputJson) {
console.log(JSON.stringify(jsonSchema, null, 2));
return;
}
console.log(getSchemaReference(section));
}
main().catch(error => {
console.error('Unexpected error:', error);
process.exit(1);
+414
View File
@@ -0,0 +1,414 @@
/**
* Schema utilities for conversation-replay
*
* Provides:
* - YAML template generation for `init` command
* - JSON Schema for IDE autocomplete
* - Schema reference output for `schema` command
*/
import type { Theme } from './types';
/**
* Generate a starter YAML template with inline documentation
*/
export function generateTemplate(theme?: Theme): string {
const selectedTheme = theme || 'chat';
return `# Conversation Replay - Demo Configuration
# Documentation: https://github.com/lennyzeltser/conversation-replay
#
# Run: conversation-replay build this-file.yaml -o output.html
# Validate: conversation-replay validate this-file.yaml
meta:
title: "My Demo Title" # Required: shown in header
description: "A brief description" # Optional: shown below title
theme: ${selectedTheme} # chat | email | slack | terminal | generic
# articleUrl: "/related-article" # Optional: "View Article" link
# annotationLabel: "Behind the Scenes" # Label for annotation steps
# autoAdvance: true # Auto-play next scenario when current ends
# Timer and visual style
# timerStyle: circle # circle | bar
# cornerStyle: rounded # rounded | straight
# initialBlur: 1 # Blur amount for play overlay (px)
# Custom colors (all optional)
# colors:
# accent: "#4f46e5" # Buttons, links (Indigo 600)
# pageBg: "#f8fafc" # Page background (Slate 50)
# canvasBg: "#ffffff" # Chat container background
# leftBg: "#eef2ff" # Left bubble background (Indigo 50)
# leftBorder: "transparent" # Left bubble border
# rightBg: "#f1f5f9" # Right bubble background (Slate 100)
# rightBorder: "transparent" # Right bubble border
# annotationText: "#475569" # Annotation text (Slate 600)
# annotationBorder: "#cbd5e1" # Annotation accent bar (Slate 300)
# Timing configuration (all in milliseconds)
# speed:
# minDelay: 3000 # Minimum delay between steps
# maxDelay: 8000 # Maximum delay between steps
# msPerWord: 200 # Reading time per word
# annotationMultiplier: 1.15 # Extra time for annotations
# upNextDelay: 2500 # "Up Next" display time
scenarios:
- id: main
title: "Scenario Title" # Shown in tab (if multiple scenarios)
participants:
- id: user1
label: "Person A"
role: left # Messages appear on left
- id: user2
label: "Person B"
role: right # Messages appear on right
steps:
# Message from a participant
- type: message
from: user1 # Must match a participant id
content: "Hello! This is a sample message."
- type: message
from: user2
content: "Hi there! Here's a response."
# footnote: "Optional italic footnote below the message"
# Message with code block
# - type: message
# from: user2
# content: "Here's some code:"
# codeBlock: |
# function example() {
# return "hello";
# }
# Annotation - educational callout with accent bar
- type: annotation
content: "This annotation explains something to the viewer."
# Transition - centered scene break
# - type: transition
# content: "Later that day..."
# Additional scenarios appear as tabs
# - id: part2
# title: "Part Two"
# participants:
# - id: analyst
# label: "Analyst"
# role: left
# steps:
# - type: message
# from: analyst
# content: "This is another scenario."
`;
}
/**
* JSON Schema for conversation-replay YAML files
*/
export const jsonSchema = {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://github.com/lennyzeltser/conversation-replay/schema.json",
"title": "Conversation Replay Demo",
"description": "Schema for conversation-replay YAML configuration files",
"type": "object",
"required": ["meta", "scenarios"],
"properties": {
"meta": {
"type": "object",
"description": "Demo metadata and configuration",
"required": ["title"],
"properties": {
"title": {
"type": "string",
"description": "Demo title shown in header",
"minLength": 1
},
"description": {
"type": "string",
"description": "Brief description shown below title"
},
"theme": {
"type": "string",
"enum": ["chat", "email", "slack", "terminal", "generic"],
"description": "Visual theme for message styling",
"default": "chat"
},
"articleUrl": {
"type": "string",
"description": "URL for 'View Article' link in header"
},
"hideHeaderInIframe": {
"type": "boolean",
"description": "Auto-hide header when embedded in iframe",
"default": true
},
"autoAdvance": {
"type": "boolean",
"description": "Auto-play next scenario when current one completes"
},
"annotationLabel": {
"type": "string",
"description": "Label shown above annotation content",
"default": "Behind the Scenes"
},
"timerStyle": {
"type": "string",
"enum": ["bar", "circle"],
"description": "Countdown timer display style",
"default": "circle"
},
"cornerStyle": {
"type": "string",
"enum": ["rounded", "straight"],
"description": "Border radius style for containers and bubbles",
"default": "rounded"
},
"initialBlur": {
"type": "number",
"description": "Blur amount in pixels for play overlay",
"default": 1,
"minimum": 0
},
"colors": {
"type": "object",
"description": "Custom color overrides",
"properties": {
"accent": { "type": "string", "description": "Primary accent color (buttons, links)" },
"pageBg": { "type": "string", "description": "Page background color" },
"canvasBg": { "type": "string", "description": "Chat container background" },
"leftBg": { "type": "string", "description": "Left participant bubble background" },
"leftBorder": { "type": "string", "description": "Left participant bubble border" },
"rightBg": { "type": "string", "description": "Right participant bubble background" },
"rightBorder": { "type": "string", "description": "Right participant bubble border" },
"tabInactiveColor": { "type": "string", "description": "Inactive tab text color" },
"annotationText": { "type": "string", "description": "Annotation text color" },
"annotationBorder": { "type": "string", "description": "Annotation accent bar color" }
}
},
"speed": {
"type": "object",
"description": "Timing configuration for playback",
"properties": {
"minDelay": { "type": "number", "description": "Minimum delay between steps (ms)", "default": 3000 },
"maxDelay": { "type": "number", "description": "Maximum delay between steps (ms)", "default": 8000 },
"msPerWord": { "type": "number", "description": "Milliseconds per word for reading time", "default": 200 },
"annotationMultiplier": { "type": "number", "description": "Time multiplier for annotations", "default": 1.15 },
"upNextDelay": { "type": "number", "description": "'Up Next' display duration (ms)", "default": 2500 }
}
}
}
},
"scenarios": {
"type": "array",
"description": "One or more conversation scenarios (shown as tabs if multiple)",
"minItems": 1,
"items": {
"type": "object",
"required": ["id", "title", "participants", "steps"],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier for this scenario",
"minLength": 1
},
"title": {
"type": "string",
"description": "Display title (shown in tab)",
"minLength": 1
},
"participants": {
"type": "array",
"description": "People/entities in this conversation",
"minItems": 1,
"items": {
"type": "object",
"required": ["id", "label"],
"properties": {
"id": {
"type": "string",
"description": "Unique identifier (referenced in message 'from' field)"
},
"label": {
"type": "string",
"description": "Display name shown above messages"
},
"role": {
"type": "string",
"enum": ["left", "right"],
"description": "Which side messages appear on",
"default": "left"
}
}
}
},
"steps": {
"type": "array",
"description": "Sequence of messages, annotations, and transitions",
"minItems": 1,
"items": {
"oneOf": [
{
"type": "object",
"description": "A message from a participant",
"required": ["type", "from", "content"],
"properties": {
"type": { "const": "message" },
"from": { "type": "string", "description": "Participant ID" },
"content": { "type": "string", "description": "Message text" },
"codeBlock": { "type": "string", "description": "Optional code/preformatted text" },
"footnote": { "type": "string", "description": "Optional italic footnote" }
}
},
{
"type": "object",
"description": "An educational annotation/callout",
"required": ["type", "content"],
"properties": {
"type": { "const": "annotation" },
"content": { "type": "string", "description": "Annotation text" }
}
},
{
"type": "object",
"description": "A scene break/transition",
"required": ["type", "content"],
"properties": {
"type": { "const": "transition" },
"content": { "type": "string", "description": "Transition text (e.g., 'Later that day...')" }
}
}
]
}
}
}
}
}
}
};
/**
* Generate human-readable schema reference for CLI output
*/
export function getSchemaReference(section?: string): string {
if (section === 'meta') {
return `
META OPTIONS
============
Required:
title: string Demo title shown in header
Optional:
description: string Brief description below title
theme: string chat | email | slack | terminal | generic (default: chat)
articleUrl: string URL for "View Article" link
annotationLabel: string Label for annotations (default: "Behind the Scenes")
autoAdvance: boolean Auto-play next scenario when current ends
hideHeaderInIframe: bool Auto-hide header when embedded (default: true)
timerStyle: string circle | bar (default: circle)
cornerStyle: string rounded | straight (default: rounded)
initialBlur: number Play overlay blur in px (default: 1)
colors: object Custom color overrides (see: schema colors)
speed: object Timing configuration (see: schema speed)
`;
}
if (section === 'colors') {
return `
COLOR OPTIONS (meta.colors)
===========================
All colors are optional CSS color values (hex, rgb, named colors, etc.)
accent: string Buttons, links (default: #4f46e5)
pageBg: string Page background (default: #f8fafc)
canvasBg: string Chat container background (default: #ffffff)
leftBg: string Left bubble background (default: #eef2ff)
leftBorder: string Left bubble border (default: transparent)
rightBg: string Right bubble background (default: #f1f5f9)
rightBorder: string Right bubble border (default: transparent)
tabInactiveColor: string Inactive tab text (default: #94a3b8)
annotationText: string Annotation text (default: #475569)
annotationBorder: string Annotation accent bar (default: #cbd5e1)
`;
}
if (section === 'speed') {
return `
SPEED OPTIONS (meta.speed)
==========================
All values are in milliseconds.
minDelay: number Minimum delay between steps (default: 3000)
maxDelay: number Maximum delay between steps (default: 8000)
msPerWord: number Reading time per word (default: 200)
annotationMultiplier: num Extra time multiplier for annotations (default: 1.15)
upNextDelay: number "Up Next" indicator duration (default: 2500)
`;
}
if (section === 'steps') {
return `
STEP TYPES
==========
MESSAGE - A chat bubble from a participant
type: message (required)
from: string Participant ID (required)
content: string Message text (required)
codeBlock: string Optional preformatted code block
footnote: string Optional italic footnote
ANNOTATION - Educational callout with accent bar
type: annotation (required)
content: string Annotation text (required)
TRANSITION - Centered scene break
type: transition (required)
content: string Transition text (required)
`;
}
// Full schema reference
return `
CONVERSATION REPLAY SCHEMA
==========================
STRUCTURE
---------
meta: Demo configuration (see: schema meta)
scenarios: Array of conversation scenarios
SCENARIO
--------
id: string Unique identifier (required)
title: string Display title for tab (required)
participants: array People in conversation (required)
steps: array Message/annotation sequence (required)
PARTICIPANT
-----------
id: string Unique ID, used in message 'from' field (required)
label: string Display name shown above messages (required)
role: string left | right (default: left)
STEP TYPES
----------
message Chat bubble (requires: from, content)
annotation Educational callout (requires: content)
transition Scene break (requires: content)
Use 'schema <section>' for details:
schema meta Meta/configuration options
schema colors Color customization
schema speed Timing configuration
schema steps Step type details
`;
}