mirror of
https://github.com/lennyzeltser/conversation-replay
synced 2026-06-21 13:55:53 +00:00
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:
@@ -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)
|
- [How This Is Useful](#how-this-is-useful)
|
||||||
- [Installation](#installation)
|
- [Installation](#installation)
|
||||||
- [Quick Start](#quick-start)
|
- [Quick Start](#quick-start)
|
||||||
|
- [CLI Reference](#cli-reference)
|
||||||
- [YAML Schema](#yaml-schema)
|
- [YAML Schema](#yaml-schema)
|
||||||
- [Output Features](#output-features)
|
- [Output Features](#output-features)
|
||||||
- [Embedding in Websites](#embedding-in-websites)
|
- [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
|
## Quick Start
|
||||||
|
|
||||||
Create a YAML file defining your conversation, then generate the HTML:
|
Generate a starter template, customize it, then build:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
conversation-replay build examples/london-scam.yaml -o demo.html
|
# Create a documented template
|
||||||
```
|
conversation-replay init demo.yaml
|
||||||
|
|
||||||
Output:
|
# Edit demo.yaml to define your conversation, then build
|
||||||
```
|
conversation-replay build demo.yaml -o demo.html
|
||||||
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)
|
|
||||||
```
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Open in browser
|
# Open in browser
|
||||||
open demo.html
|
open demo.html
|
||||||
```
|
```
|
||||||
|
|
||||||
The generated replay is responsive, supports dark mode automatically, and looks nice across various browsers and devices.
|
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>` — Override theme (`chat`, `email`, `slack`, `terminal`, `generic`)
|
||||||
|
- `--no-header` — 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
|
## YAML Schema
|
||||||
|
|
||||||
|
Run `conversation-replay schema` for a quick reference, or see below for details.
|
||||||
|
|
||||||
### Basic Structure
|
### Basic Structure
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -271,8 +293,10 @@ conversation-replay/
|
|||||||
│ ├── cli.ts # CLI entry point
|
│ ├── cli.ts # CLI entry point
|
||||||
│ ├── parser.ts # YAML parsing & validation
|
│ ├── parser.ts # YAML parsing & validation
|
||||||
│ ├── generator.ts # HTML generation (CSS/JS injection)
|
│ ├── generator.ts # HTML generation (CSS/JS injection)
|
||||||
|
│ ├── schema.ts # Template generation & schema utilities
|
||||||
│ └── types.ts # TypeScript definitions
|
│ └── types.ts # TypeScript definitions
|
||||||
├── examples/ # Demo YAML files
|
├── examples/ # Demo YAML files
|
||||||
|
├── schema.json # JSON Schema for IDE autocompletion
|
||||||
└── package.json
|
└── package.json
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -282,14 +306,11 @@ conversation-replay/
|
|||||||
# Install dependencies
|
# Install dependencies
|
||||||
bun install
|
bun install
|
||||||
|
|
||||||
# Build
|
# Run CLI during development
|
||||||
bun run src/cli.ts build examples/london-scam.yaml -o demo.html
|
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
|
bun run src/cli.ts validate examples/london-scam.yaml
|
||||||
|
bun run src/cli.ts init my-demo.yaml
|
||||||
# Build with options
|
bun run src/cli.ts schema
|
||||||
bun run src/cli.ts build examples/london-scam.yaml -o demo.html --theme email --no-header
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -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/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/generator.ts` | The core engine. Generates the HTML, CSS, and runtime JavaScript. |
|
||||||
| `src/parser.ts` | Input validation logic. |
|
| `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
|
### Architecture
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "conversation-replay",
|
"name": "conversation-replay",
|
||||||
"version": "0.1.13",
|
"version": "0.1.14",
|
||||||
"description": "Create annotated replays of text conversations",
|
"description": "Create annotated replays of text conversations",
|
||||||
"main": "dist/cli.js",
|
"main": "dist/cli.js",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
|
"schema.json",
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
+292
@@ -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
@@ -5,12 +5,16 @@
|
|||||||
* Usage:
|
* Usage:
|
||||||
* conversation-replay build <scenario.yaml> -o <output.html>
|
* conversation-replay build <scenario.yaml> -o <output.html>
|
||||||
* conversation-replay validate <scenario.yaml>
|
* conversation-replay validate <scenario.yaml>
|
||||||
|
* conversation-replay init <output.yaml>
|
||||||
|
* conversation-replay schema [section]
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { parseArgs } from 'util';
|
import { parseArgs } from 'util';
|
||||||
import { loadDemo, ParseError } from './parser';
|
import { loadDemo, ParseError } from './parser';
|
||||||
import { buildDemo } from './generator';
|
import { buildDemo } from './generator';
|
||||||
|
import { generateTemplate, getSchemaReference, jsonSchema } from './schema';
|
||||||
import type { Theme } from './types';
|
import type { Theme } from './types';
|
||||||
|
import packageJson from '../package.json';
|
||||||
|
|
||||||
const HELP = `
|
const HELP = `
|
||||||
conversation-replay - Create annotated replays of text conversations from YAML
|
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:
|
Usage:
|
||||||
conversation-replay build <scenario.yaml> -o <output.html> [options]
|
conversation-replay build <scenario.yaml> -o <output.html> [options]
|
||||||
conversation-replay validate <scenario.yaml>
|
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:
|
Commands:
|
||||||
build Generate HTML from a scenario file
|
build Generate HTML from a scenario file
|
||||||
validate Check a scenario file for errors
|
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:
|
Options:
|
||||||
-o, --output <path> Output HTML file path (required for build)
|
-o, --output <path> Output HTML file path (required for build)
|
||||||
--theme <theme> Override theme (chat, email, slack, terminal, generic)
|
--theme <theme> Override theme (chat, email, slack, terminal, generic)
|
||||||
--no-header Exclude the demo header
|
--no-header Exclude the demo header
|
||||||
|
--json Output JSON schema (for schema command)
|
||||||
-h, --help Show this help message
|
-h, --help Show this help message
|
||||||
|
-v, --version Show version number
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
conversation-replay build demo.yaml -o demo.html
|
conversation-replay build demo.yaml -o demo.html
|
||||||
conversation-replay build demo.yaml -o demo.html --theme email
|
conversation-replay init my-demo.yaml
|
||||||
conversation-replay validate demo.yaml
|
conversation-replay schema meta
|
||||||
|
conversation-replay info
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const VALID_THEMES = ['chat', 'email', 'slack', 'terminal', 'generic'];
|
const VALID_THEMES = ['chat', 'email', 'slack', 'terminal', 'generic'];
|
||||||
@@ -45,21 +58,39 @@ async function main() {
|
|||||||
output: { type: 'string', short: 'o' },
|
output: { type: 'string', short: 'o' },
|
||||||
theme: { type: 'string' },
|
theme: { type: 'string' },
|
||||||
'no-header': { type: 'boolean' },
|
'no-header': { type: 'boolean' },
|
||||||
|
json: { type: 'boolean' },
|
||||||
help: { type: 'boolean', short: 'h' },
|
help: { type: 'boolean', short: 'h' },
|
||||||
|
version: { type: 'boolean', short: 'v' },
|
||||||
},
|
},
|
||||||
allowPositionals: true,
|
allowPositionals: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (values.version) {
|
||||||
|
console.log(`conversation-replay v${packageJson.version}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
if (values.help || positionals.length === 0) {
|
if (values.help || positionals.length === 0) {
|
||||||
console.log(HELP);
|
console.log(HELP);
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
const command = positionals[0];
|
const command = positionals[0];
|
||||||
const inputFile = positionals[1];
|
const arg1 = positionals[1];
|
||||||
|
|
||||||
if (!inputFile) {
|
// Commands that don't require an input file
|
||||||
console.error('Error: No input file specified\n');
|
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);
|
console.log(HELP);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@@ -74,11 +105,15 @@ async function main() {
|
|||||||
try {
|
try {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'build':
|
case 'build':
|
||||||
await handleBuild(inputFile, values);
|
await handleBuild(arg1, values);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'validate':
|
case 'validate':
|
||||||
await handleValidate(inputFile);
|
await handleValidate(arg1);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'init':
|
||||||
|
await handleInit(arg1, values.theme as Theme | undefined);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
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 => {
|
main().catch(error => {
|
||||||
console.error('Unexpected error:', error);
|
console.error('Unexpected error:', error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
|||||||
+414
@@ -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
|
||||||
|
`;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user