DemoScript is a framework for creating scripted, shareable product demonstrations. Users write simple YAML files describing demo steps, and the framework provides:
- A beautiful web UI for stepping through demos
- Live execution mode for presenters (runs real commands/APIs)
- Viewer mode for sharing (plays back recorded responses)
- Static site generation for portable, self-contained demos
- Simplicity - A novice can write a demo script in YAML
- Shareability - Export demos as static sites that work anywhere
- Flexibility - Support REST APIs, shell commands, and explanatory slides
- Dual Mode - Live execution for presenters, recorded playback for viewers
┌─────────────────────────────────────────────────────────────────┐
│ User Workflow │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Write YAML ──► 2. Record ──► 3. Build ──► 4. Share │
│ │
│ demo.yaml recordings.json dist/index.html URL/file │
│ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ System Components │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ CLI Tool │ │ Dev Server │ │ Static Builder │ │
│ │ │ │ │ │ │ │
│ │ - init │ │ - REST proxy │ │ - Parse YAML │ │
│ │ - new │ │ - Shell exec │ │ - Bundle React app │ │
│ │ - serve │ │ - WebSocket │ │ - Embed recordings │ │
│ │ - record │ │ (live UI) │ │ - Output static HTML │ │
│ │ - build │ │ │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ React Web UI │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────────────┐ │ │
│ │ │ Stepper │ │ Slide │ │ REST │ │ Shell │ │ │
│ │ │ │ │ Viewer │ │ Viewer │ │ Viewer │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────┐ │ │
│ │ │ Form Builder │ │ Result Renderer │ │ Controls │ │ │
│ │ └─────────────────┘ └─────────────────┘ └─────────────┘ │ │
│ │ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
The DemoScript Visual Editor is a CLI-based tool for creating demos interactively without writing YAML by hand.
$ demoscript edit
→ Opens browser to http://localhost:3002/editor
┌─────────────────────────────────────────────────────────────────┐
│ Mode: (•) OpenAPI ( ) Custom [Clear All] │
├─────────────────────────────────────────────────────────────────┤
│ OPENAPI MODE: │ CUSTOM MODE: │
│ URL: [spec url______] [Fetch] │ Base URL: [https://api...] │
│ │ Method: [POST ▼] │
│ ENDPOINTS EXECUTION │ Path: [/users] │
│ [Search...] ┌──────────┐ │ Headers: [textarea] │
│ │ Form │ │ Body: [JSON textarea] │
│ Users │ fields │ │ │
│ ├ GET /users │ from │ │ [Execute] │
│ └ POST /users │ OpenAPI │ │ │
│ └──────────┘ │ │
│ [Execute] [+ Add to Demo] │ [+ Add to Demo] │
├────────────────────────────────┴────────────────────────────────┤
│ DEMO STEPS │ EXPORT │
│ [1] POST /users [Edit][Del] │ [Copy YAML] │
│ [2] GET /users/$id [Edit][Del] │ [Download YAML] │
│ │ │
│ Variables: $userId = 123 │ │
└────────────────────────────────┴────────────────────────────────┘
- EndpointBrowser: Fetches OpenAPI spec, groups endpoints by tag, provides search
- ExecutionPanel: Generates forms from OpenAPI, executes requests, displays responses
- CustomEndpoint: Manual mode for entering method/URL/body without OpenAPI
- StepSequence: Drag-and-drop list of demo steps with edit/delete
- ExportPanel: YAML generation with copy/download functionality
Types and utilities:
BuilderStep,BuilderState: Type definitionsgenerateDemoYaml(): Converts builder state to YAML formatstepToYaml(): Converts individual step to YAML lines
Endpoint browser functions:
groupEndpointsByTag(): Groups endpoints by their first tagsearchEndpoints(): Filters endpoints by query stringgetAllEndpoints(): Extracts all endpoints from specgetParameterFields(): Generates form fields for path/query params
demoscript/
├── packages/
│ ├── cli/ # CLI tool (Node.js)
│ │ ├── src/
│ │ │ ├── index.ts # Entry point
│ │ │ ├── commands/
│ │ │ │ ├── init.ts
│ │ │ │ ├── new.ts
│ │ │ │ ├── serve.ts
│ │ │ │ ├── record.ts
│ │ │ │ └── build.ts
│ │ │ ├── server/ # Dev server for live execution
│ │ │ │ ├── index.ts
│ │ │ │ ├── rest-proxy.ts
│ │ │ │ └── shell-executor.ts
│ │ │ └── builder/ # Static site generator
│ │ │ ├── index.ts
│ │ │ └── templates/
│ │ └── package.json
│ │
│ └── ui/ # React web UI
│ ├── src/
│ │ ├── App.tsx
│ │ ├── components/
│ │ │ ├── DemoRunner.tsx
│ │ │ ├── StepViewer.tsx
│ │ │ ├── SlideStep.tsx
│ │ │ ├── RestStep.tsx
│ │ │ ├── ShellStep.tsx
│ │ │ ├── FormBuilder.tsx
│ │ │ ├── ResultRenderer.tsx
│ │ │ ├── Stepper.tsx
│ │ │ └── Controls.tsx
│ │ ├── context/
│ │ │ └── DemoContext.tsx
│ │ ├── hooks/
│ │ │ ├── useDemo.ts
│ │ │ └── useExecution.ts
│ │ ├── types/
│ │ │ └── schema.ts
│ │ └── lib/
│ │ ├── variable-substitution.ts
│ │ └── result-formatting.ts
│ ├── index.html
│ └── package.json
│
├── examples/
│ ├── hello-world/ # Minimal intro demo
│ │ └── demo.yaml
│ ├── feature-showcase/ # Comprehensive feature demo
│ │ ├── demo.yaml
│ │ └── recordings.json
│ └── browser-demo/ # Browser automation demo
│ └── demo.yaml
│
├── package.json # Monorepo root
├── DESIGN.md # This document
└── README.md
# demo.yaml
# Required metadata
title: "Demo Title"
description: "What this demo shows"
# Optional metadata
version: "1.0"
author: "Your Name"
tags: ["api", "tutorial"]
# Optional global settings
settings:
base_url: "http://localhost:8000" # Default base URL for REST calls
theme:
logo: "./assets/logo.png" # Optional logo image
preset: "purple" # Color preset: purple|blue|green|teal|orange|rose
primary: "#8b5cf6" # Custom primary color (overrides preset)
accent: "#06b6d4" # Custom accent color (overrides preset)
mode: "auto" # Force light/dark mode: auto|light|dark
polling:
interval: 2000 # Default polling interval (ms)
max_attempts: 30 # Default max polling attempts
effects:
confetti: true # Fire confetti on step completion
sounds: true # Play success/error sounds
transitions: true # Animate step changes
counters: true # Animate numeric values
# Demo steps (required)
steps:
- slide: ...
- rest: ...
- shell: ...Displays markdown content for explanations.
- slide: |
# Welcome
This demo shows **feature X**.
We'll cover:
- Step one
- Step two
# Optional: title shown in stepper (defaults to first heading)
title: "Introduction"Makes HTTP API calls.
- rest: POST /api/tokens
title: "Create Token"
description: "Deploy a new ERC-20 token"
# Optional: override base_url for this step
base_url: "http://different-server:8080"
# Request headers (optional)
headers:
Authorization: "Bearer $authToken"
Content-Type: "application/json"
# Request body (optional, for POST/PUT/PATCH)
body:
name: "Demo Dollar"
symbol: "DEMOD"
decimals: 18
# Form fields - allows user to edit values before execution
form:
- name: name
label: "Token Name"
type: text # text, number, select, textarea
default: "Demo Dollar"
required: true
- name: symbol
label: "Symbol"
type: text
default: "DEMOD"
- name: decimals
label: "Decimals"
type: number
default: 18
# Store values from response for use in later steps
# Use JSON paths for response body, or special keywords like _status
save:
tokenAddress: tokenAddress # save response.tokenAddress as $tokenAddress
tokenSymbol: symbol # save response.symbol as $tokenSymbol
httpStatus: _status # save HTTP status code (200, 404, etc.)
# How to display results
results:
- key: id
label: "Resource ID"
type: text
- key: username
label: "Username"
link: github.user # Shorthand: handler.template (auto-infers type: id)
# Polling for async operations (optional)
wait_for: abtJobId # Response field containing job ID
poll:
endpoint: "http://localhost:8088/api/jobs/$jobId"
success_when: "status == 'completed'"
failure_when: "status == 'failed'"
interval: 2000
max_attempts: 30REST save keywords:
| Keyword | Description |
|---|---|
_status |
HTTP status code (200, 404, 500, etc.) |
path.to.value |
JSON path extraction from response body (e.g., data.user.id) |
Executes shell commands.
- shell: docker ps --format "table {{.Names}}\t{{.Status}}"
title: "List Containers"
description: "Show running Docker containers"
# Optional: shell type (defaults to system default)
shell_type: bash # bash, powershell, cmd
# Optional: working directory
workdir: /path/to/project
# Optional: require confirmation before running
confirm: true
# Optional: environment variables
env:
NODE_ENV: production
# Optional: capture command outputs
save:
containerList: stdout # Standard output → $containerList
errors: stderr # Standard error → $errors
exitCode: status # Exit code (0=success) → $exitCodeShell save keywords:
| Keyword | Description |
|---|---|
stdout |
Standard output from the command |
stderr |
Standard error output |
status |
Exit code (0 = success, non-zero = error) |
output |
Legacy alias for stdout (for backward compatibility) |
Unlike REST steps where save maps JSON response paths, shell steps use these special keywords to capture different parts of command execution.
Opens or displays a URL.
- browser: https://app.example.com/dashboard
title: "View Dashboard"
description: "See the result in the web UI"
# Optional: screenshot to show in viewer mode
screenshot: ./assets/dashboard.pngDisplays syntax-highlighted code snippets (display only, not executed).
- code: |
const response = await fetch('/api/users');
const users = await response.json();
console.log(users);
language: javascript
filename: example.js
title: "JavaScript Example"
# Optional: highlight specific lines
highlight: [2, 3]Supported languages: javascript, typescript, python, bash, json, yaml, sql, go, rust, java, c, cpp, and more.
Adds a timed delay with visual countdown.
- wait: 2000 # Duration in milliseconds
message: "Processing..." # Optional message to display
title: "Brief Pause"Validates conditions and shows pass/fail results.
- assert: "$userId == 1"
title: "Verify User ID"
description: "Ensure we fetched the correct user"
message: "Expected user ID 1" # Custom failure messageSupported operators:
==(equals)!=(not equals)>,>=,<,<=(numeric comparisons)
Supports nested paths: $response.data.id == 123
Executes GraphQL queries and mutations.
- graphql: |
query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
endpoint: "https://api.example.com/graphql"
variables:
id: "$userId"
headers:
Authorization: "Bearer $token"
save:
userName: data.user.name
title: "Fetch User"Queries databases (MongoDB, PostgreSQL, MySQL).
# MongoDB example
- db: findOne
collection: users
query: { email: "$userEmail" }
projection: { password: 0 }
save:
userId: _id
userName: name
title: "Find User"
# MongoDB operations: find, findOne, insertOne, updateOne, deleteOne
# SQL example (PostgreSQL/MySQL)
- db: query
type: postgres
query: "SELECT * FROM users WHERE id = $userId"
save:
userName: name
title: "Query User"Database connection is configured in settings:
settings:
database_url: "mongodb://localhost:27017/mydb"Collects user input without making an API call. Useful for gathering information that will be used in later steps.
- form: "User Information"
description: "Please provide your details"
fields:
- name: username
label: "Username"
type: text
required: true
- name: role
label: "Role"
type: select
options:
- { value: admin, label: "Administrator" }
- { value: user, label: "Standard User" }
- name: notifications
label: "Enable Notifications"
type: toggle
default: true
submit_label: "Continue"
save:
userData: $formData # Save entire form data object
selectedRole: $role # Save specific field valueForm data is automatically available as $fieldName for each field in subsequent steps.
Displays animated terminal playback for installation tutorials, CLI demos, or command sequences.
- terminal: |
$ npm install -g demoscript
added 42 packages in 2.3s
$ demoscript --version
DemoScript v1.0.0
$ demoscript serve examples/hello-world
Server running at http://localhost:3000
title: "Installation"
typing_speed: 30 # ms per character (default: 30)
output_delay: 200 # ms before showing command output (default: 200)
prompt: "$" # Prompt character (default: "$")
theme: dark # dark | light | matrixLines starting with the prompt character are "typed" with animation; all other lines appear as command output after the delay.
Waits for an asynchronous operation to complete with visual progress stages.
- poll: /jobs/$jobId/status
title: "Processing Job"
success_when: status == 'completed'
failure_when: status == 'failed'
interval: 2000 # Poll every 2 seconds (default: 2000)
max_attempts: 30 # Give up after 30 attempts (default: 30)
stages: # Optional visual progress indicators
- label: "Queued"
when: status == 'queued'
- label: "Processing"
when: status == 'processing'
- label: "Finalizing"
when: status == 'finalizing'
save:
result: data # Save response data when completeThe poll step makes GET requests to the endpoint until success_when evaluates to true, failure_when evaluates to true, or max_attempts is reached.
DemoScript can display animated flow diagrams alongside steps to visualize architecture, data flow, or process sequences.
Add diagram settings to show a flow chart:
settings:
diagram:
chart: |
flowchart LR
A[Client] --> B[API Gateway]
B --> C[Auth Service]
B --> D[Data Service]
D --> E[(Database)]
position: toggle # sticky | sidebar | toggle (default: toggle)
height: 300 # Height in pixels (default: 300)Each step can highlight specific nodes or edges in the diagram:
steps:
- rest: POST /auth/login
title: "Authenticate"
diagram: "A->B" # Highlight edge from A to B
- rest: GET /users
title: "Fetch Users"
diagram: "B->D" # Highlight edge from B to D
- slide: |
# Data Stored
User data is persisted to the database.
diagram: "D->E" # Highlight edge from D to ESupported syntax:
"NodeA->NodeB"- Highlight edge between two nodes"NodeA"- Highlight single node only
Uses Mermaid flowchart syntax. The diagram updates automatically as steps progress, showing completed paths and the current operation.
DemoScript can automatically generate form fields from OpenAPI/Swagger specifications. This reduces manual form configuration and keeps demos in sync with API schemas.
Add the OpenAPI spec URL to settings:
settings:
base_url: "http://localhost:8000"
openapi: "http://localhost:8000/docs/json" # OpenAPI 3.0 spec URLPer-step override:
- rest: POST /external-api/users
openapi: "http://external-api.com/docs/json" # Override for this stepThe loader fetches the OpenAPI spec and generates form fields from request body schemas:
┌──────────────────────────────────────────────────────────────┐
│ OpenAPI Form Generation │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. Fetch OpenAPI spec ──► Parse JSON │
│ │
│ 2. Match endpoint ──► GET /tokens → paths["/tokens"]["get"] │
│ │
│ 3. Extract requestBody.content.application/json.schema │
│ │
│ 4. Resolve $refs and allOf merges │
│ │
│ 5. Convert schema properties → FormField[] │
│ - type: string → text │
│ - type: integer/number → number │
│ - type: boolean → select (true/false) │
│ - enum → select with options │
│ - array/object → textarea (JSON) │
│ │
│ 6. Apply merge priority (smart merge): │
│ OpenAPI schema → defaults → form (partial override) │
│ │
└──────────────────────────────────────────────────────────────┘
- OpenAPI schema (base) - Field types, required flags, descriptions from API spec
defaults:- Override default values only, preserves OpenAPI field definitionform:- Partial override, inherits from OpenAPI + defaults, only overrides specified properties
Fields in form: inherit all properties from OpenAPI and defaults:, so you only specify what you want to change.
Example:
- rest: POST /tokens
# OpenAPI generates: name (text, required), symbol (text, required), decimals (number)
defaults:
name: "Demo Dollar" # Override default only
symbol: "DEMOD"
decimals: 18
form:
- name: decimals # Partial override - just add label and readonly
label: "Token Decimals"
readonly: true # Inherits default: 18 from defaults section aboveOpenAPI specs are cached in memory with a 5-minute TTL to avoid repeated fetches during development.
Key files:
packages/cli/src/lib/openapi.ts- OpenAPI fetcher and form generatorpackages/cli/src/lib/loader.ts- Integrates OpenAPI processing
DemoScript includes configurable visual effects for engaging presentations.
Configure effects in the settings block:
settings:
effects:
confetti: true # Fire confetti particles on step completion
sounds: true # Play success/error audio feedback
transitions: true # Animate step navigation (slide/fade)
counters: true # Animate numeric values counting upAll effects are enabled by default for maximum engagement. Each can be individually disabled.
| Effect | Description | Implementation |
|---|---|---|
confetti |
Celebration particles on step success | Uses canvas-confetti library |
sounds |
Audio feedback (success chord, error tone) | Web Audio API synthesis |
transitions |
Slide animation between steps | CSS transforms with React |
counters |
Number counting animation | react-countup library |
Effects are managed centrally using the Separation of Concerns principle:
┌──────────────────────────────────────────────────────────────┐
│ Effect Orchestration │
├──────────────────────────────────────────────────────────────┤
│ │
│ StepViewer ──► useStepEffects() ──► triggers effects │
│ │
│ Step components ONLY render content │
│ Effects hook watches stepStatuses for changes │
│ Configuration read from state.config.settings.effects │
│ │
└──────────────────────────────────────────────────────────────┘
Key files:
packages/ui/src/hooks/useStepEffects.ts- Centralized effect triggeringpackages/ui/src/components/effects/- Effect components (Confetti, SoundEffects, etc.)packages/ui/src/types/schema.ts- EffectsSettings interface
DemoScript supports customizable color themes via YAML configuration.
settings:
theme:
# Option 1: Use a preset theme
preset: "purple" # purple (default) | blue | green | teal | orange | rose
# Option 2: Custom colors (overrides preset)
primary: "#8b5cf6" # Main brand color (buttons, gradients, accents)
accent: "#06b6d4" # Secondary color (gradient endpoints, highlights)
# Option 3: Force light/dark mode
mode: "auto" # auto | light | dark| Preset | Primary | Accent | Use Case |
|---|---|---|---|
purple |
#8b5cf6 | #06b6d4 | Default DemoScript look |
blue |
#3b82f6 | #8b5cf6 | Corporate, professional |
green |
#10b981 | #3b82f6 | Fintech, nature |
teal |
#14b8a6 | #f59e0b | Modern, fresh |
orange |
#f97316 | #8b5cf6 | Energetic, warm |
rose |
#f43f5e | #8b5cf6 | Bold, vibrant |
The theme system uses CSS custom properties for dynamic color injection:
┌──────────────────────────────────────────────────────────────┐
│ Theme System Flow │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. YAML config loads ──► theme settings extracted │
│ │
│ 2. DemoRunner ──► getThemeColors() ──► resolves colors │
│ (preset lookup or custom colors) │
│ │
│ 3. applyThemeColors() ──► injects CSS variables on <html> │
│ --color-primary: #8b5cf6 │
│ --color-accent: #06b6d4 │
│ --color-primary-rgb: 139, 92, 246 │
│ --color-accent-rgb: 6, 182, 212 │
│ │
│ 4. Components use CSS variables via Tailwind classes │
│ from-theme-primary, text-theme-accent, etc. │
│ │
└──────────────────────────────────────────────────────────────┘
Key files:
packages/ui/src/lib/theme-colors.ts- Presets and color utilitiespackages/ui/src/context/ThemeContext.tsx- Theme state and mode managementpackages/ui/src/index.css- CSS variables and utility classespackages/ui/tailwind.config.js- Tailwind color configuration
DemoScript supports a three-tier settings hierarchy for fine-grained control:
- Defaults - Built-in values (e.g.,
show_curl: false,confetti: true) - Global - Defined in
settings:block, applies to all steps - Step - Per-step overrides, highest priority
Resolution Pattern:
const value = step.setting ?? globalSettings?.setting ?? defaultValue;| Setting | Global | Step | Default | Description |
|---|---|---|---|---|
show_curl |
✓ | ✓ | false |
Show curl command for REST requests |
results_position |
✓ | ✓ | 'auto' |
Results panel position: left, right, or auto |
effects.confetti |
✓ | ✓ | true |
Fire confetti on step completion |
effects.sounds |
✓ | ✓ | true |
Play success/error sounds |
effects.counters |
✓ | ✓ | true |
Animate numeric values in results |
settings:
show_curl: true # Global: show curl for all REST steps
results_position: auto # Global: auto-detect based on form fields
effects:
confetti: true
sounds: true
steps:
- rest: GET /users
# Inherits: show_curl=true, results_position=auto, confetti=true
- rest: POST /users
show_curl: false # Override: hide curl for this step
results_position: left # Override: force results to left column
effects:
confetti: false # Override: no celebration for this step
form:
- name: email
type: textKey files:
packages/ui/src/lib/step-settings.ts- Settings resolution utilitypackages/ui/src/hooks/useStepEffects.ts- Step-level effects handling
Steps can define unique identifiers and navigation targets for non-linear demo flows.
Assign a unique ID to any step for targeting:
- id: summary
slide: |
# Summary
...Jump to a specific step after completion:
- rest: POST /api/data
title: "Create Data"
goto: summary # Jump to step with id: summary after this stepSlide steps can present interactive choices:
- slide: |
# Choose Your Path
Would you like to see more details or skip to the summary?
choices:
- label: "See Details"
description: "Continue with full demo"
goto: details
- label: "Skip to Summary"
description: "Jump ahead"
goto: summary
- id: details
slide: |
# Detailed Information
...
goto: summary # Continue to summary after details
- id: summary
slide: |
# Summary
...Organize related steps into collapsible groups:
steps:
- group: "Setup"
description: "Initial configuration"
collapsed: false # Optional: start collapsed
steps:
- slide: |
# Getting Started
...
- rest: POST /api/setup
- group: "Main Flow"
steps:
- rest: GET /api/data
- rest: POST /api/process
# Ungrouped steps still work
- slide: |
# Conclusion
...Groups appear as collapsible sections in the UI stepper.
When viewing a demo in recorded mode, users can modify form field values. If values differ from the recording defaults, a "Try with your values" button appears, allowing live API execution while still in recorded mode.
This enables:
- Viewers to experiment with different inputs
- Comparison between recorded and live responses
- Interactive demos without full live mode
DemoScript supports two syntax styles for step definitions:
- rest: GET /users/1
title: "Fetch User"
save:
userName: name- step: rest
method: GET
path: /users/1
title: "Fetch User"
save:
userName: nameBoth syntaxes are fully supported and can be mixed within the same demo. The explicit syntax offers better IDE autocomplete and clearer structure for complex steps.
Explicit field mappings:
| Step Type | Concise Field | Explicit Fields |
|---|---|---|
| slide | slide: |
step: slide, content: |
| rest | rest: |
step: rest, method:, path: |
| shell | shell: |
step: shell, command: |
| browser | browser: |
step: browser, url: |
| code | code: |
step: code, source: |
| wait | wait: |
step: wait, duration: |
| assert | assert: |
step: assert, condition: |
| graphql | graphql: |
step: graphql, query: |
| db | db: |
step: db, operation: |
Variables saved from previous steps can be used anywhere with $variableName:
- rest: POST /api/tokens
save:
tokenId: id
- rest: GET /api/tokens/$tokenId # In endpoint
title: "Get Token $tokenId" # In title
headers:
X-Token: $tokenId # In headers
- shell: echo "Token ID is $tokenId" # In shell commands
- slide: |
Created token with ID: **$tokenId** # In markdownLink handlers create clickable links to external services. Define them in settings.links:
settings:
links:
# GitHub integration
github:
user: "https://github.com/{value}"
issue: "https://github.com/org/repo/issues/{value}"
pr: "https://github.com/org/repo/pull/{value}"
# Jira integration
jira:
issue: "https://mycompany.atlassian.net/browse/{value}"
# Blockchain explorer
polygonscan:
address: "https://amoy.polygonscan.com/address/{value}"
tx: "https://amoy.polygonscan.com/tx/{value}"Then reference handlers in results:
results:
# Shorthand syntax (preferred) - auto-infers type: id
- key: username
link: github.user # Opens https://github.com/{username}
- key: ticketId
link: jira.issue # Opens Jira issue page
- key: walletAddress
link: polygonscan.address
# Explicit syntax (backwards compatible)
- key: author
type: id
link: github
link_key: userThe {value} placeholder is replaced with the actual result value.
Initialize a new DemoScript project.
$ demoscript init
Creating new DemoScript project...
? Project name: my-demos
? Default base URL (optional): http://localhost:8000
Created:
my-demos/
├── demoscript.config.yaml
├── demos/
│ └── example/
│ └── demo.yaml
└── package.json
Run: cd my-demos && npm installCreate a new demo from template.
$ demoscript new product-tour
Created demos/product-tour/demo.yaml
Edit the file and run: demoscript serve product-tourStart dev server for live demo presentation.
$ demoscript serve feature-showcase
Starting DemoScript server...
Demo: feature-showcase
Mode: Live execution enabled
URL: http://localhost:3000
Press 'r' to reload, 'q' to quitExecute all steps and save responses.
$ demoscript record feature-showcase
Recording demo: feature-showcase
[1/6] Introduction (slide) - skipped
[2/6] Create Token (rest) - POST /api/tokens
✓ Response saved (234ms)
[3/6] Create Vault (rest) - POST /api/vaults
✓ Response saved (189ms)
[4/6] Deposit Funds (rest) - POST /api/deposits
✓ Polling complete (4.2s)
[5/6] Check Balance (rest) - GET /api/plvs/xxx
✓ Response saved (45ms)
[6/6] Summary (slide) - skipped
Saved: demos/feature-showcase/recordings.jsonExport demo as static site.
$ demoscript build feature-showcase
Building demo: feature-showcase
✓ Parsed demo.yaml
✓ Loaded recordings.json
✓ Bundled UI components
✓ Generated static assets
Output: dist/feature-showcase/
├── index.html (487 KB)
├── assets/
└── recordings.json
Deploy anywhere or open index.html directly.Build all demos with gallery index.
$ demoscript build --all
Building all demos...
✓ feature-showcase
✓ product-tour
✓ api-overview
Generated gallery: dist/index.html
Total size: 1.4 MBExport demo as MP4 video.
$ demoscript export-video examples/browser-demo -o demo.mp4
Exporting video: examples/browser-demo
Resolution: 1280x720
FPS: 30
Step delay: 2000ms
Demo: Browser Step Demo
Steps: 10
Capturing frames...
Frame 1: Initial
Frame 31: Step 2
...
✓ Captured 300 frames
Encoding video...
✓ Video saved: demo.mp4Uses Puppeteer to capture frames and ffmpeg to encode. Requires a static build first.
Export demo as animated GIF.
$ demoscript export-gif examples/browser-demo -o demo.gif --optimize
Exporting GIF: examples/browser-demo
Width: 800px
FPS: 10
Step delay: 2000ms
Optimize: yes
Demo: Browser Step Demo
Steps: 10
Capturing frames...
✓ Captured 100 frames
Encoding GIF (optimizing)...
✓ GIF saved: demo.gif (2.35 MB)With --optimize, uses two-pass encoding with palette generation for better quality.
During demoscript record, browser steps are captured using Puppeteer:
┌──────────────────────────────────────────────────────────────┐
│ Recording Flow │
├──────────────────────────────────────────────────────────────┤
│ │
│ Browser Step ──► Puppeteer Launch ──► Navigate to URL │
│ │
│ Wait for page load ──► Capture Screenshot ──► Save PNG │
│ │
│ Output: assets/screenshots/step-<id>.png │
│ │
└──────────────────────────────────────────────────────────────┘
Key files:
packages/cli/src/lib/screenshot.ts- Puppeteer wrapperpackages/cli/src/commands/record.ts- Browser step handling
Export commands use a frame-based approach:
┌──────────────────────────────────────────────────────────────┐
│ Export Flow │
├──────────────────────────────────────────────────────────────┤
│ │
│ 1. Load static build ──► Open in Puppeteer │
│ │
│ 2. Navigate steps ──► Capture PNG frames │
│ (use keyboard navigation, delay between steps) │
│ │
│ 3. Encode frames ──► ffmpeg ──► MP4/GIF output │
│ │
│ 4. Cleanup temp frames │
│ │
└──────────────────────────────────────────────────────────────┘
Key files:
packages/cli/src/lib/video-encoder.ts- ffmpeg wrapperpackages/cli/src/commands/export-video.ts- Video exportpackages/cli/src/commands/export-gif.ts- GIF export
Dependencies:
puppeteer- Browser automation for screenshot captureffmpeg-static- Bundled ffmpeg binaryfluent-ffmpeg- Node.js ffmpeg wrapper
<DemoProvider>
<DemoRunner>
<Header />
<Stepper steps={...} currentStep={...} />
<StepViewer>
{step.type === 'slide' && <SlideStep />}
{step.type === 'rest' && <RestStep />}
{step.type === 'shell' && <ShellStep />}
</StepViewer>
<Controls />
</DemoRunner>
</DemoProvider>
Main orchestrator. Manages current step, execution state, stored variables.
Visual progress indicator showing all steps with completion status.
Renders markdown content with syntax highlighting for code blocks.
Shows:
- Method + endpoint (with syntax highlighting)
- Editable form for request body
- Request preview (curl-like display)
- Response with formatted results
- Polling status when applicable
Shows:
- Command with typing animation
- Output with ANSI color support
- Working directory indicator
Dynamically generates form fields from YAML config.
Formats response values based on type (ref, currency, etc.) with appropriate links.
Navigation buttons, keyboard shortcuts, mode toggle (live/recorded).
- Requires dev server running (
demoscript serve) - Actually executes REST calls and shell commands
- Shows real responses
- Can save new recordings
- Works from static build
- No server required
- Plays back saved responses
- Form edits don't affect output (informational only)
The UI detects which mode based on:
- If running from dev server → Live mode available
- If running from static build → Recorded mode only
A toggle in the UI allows switching to recorded mode even when live is available (useful for testing recordings).
- Set up monorepo structure (npm workspaces)
- Create TypeScript types for YAML schema
- Build YAML parser with validation
- Create basic CLI scaffolding
- Port reusable components from relink demo-ui
- Create DemoRunner component
- Implement Stepper component
- Build SlideStep (markdown rendering)
- Build RestStep (form, request preview, results)
- Implement variable substitution
- Create dev server with REST proxy
- Implement shell command executor
- Add polling support for async operations
- Build recording capture system
- Create Vite-based build pipeline
- Embed demo config and recordings in build
- Generate standalone HTML/JS bundles
- Add gallery index generation
- Create ReLink on-chain demo as example
- Add keyboard shortcuts
- Implement theming/branding
- Write documentation
- Test end-to-end workflow
| Component | Technology | Rationale |
|---|---|---|
| Monorepo | npm workspaces | Simple, no extra tooling |
| CLI | Node.js + Commander | Standard, well-supported |
| Server | Express + ws | Simple REST + WebSocket |
| UI | React 18 + TypeScript | Familiar, component-based |
| Bundler | Vite | Fast builds, good DX |
| Styling | Tailwind CSS | Rapid UI development |
| Markdown | react-markdown + remark-gfm | GitHub-flavored markdown |
| YAML | js-yaml | Standard YAML parser |
| Syntax Highlighting | Prism or Shiki | Code blocks in slides |
- Feature showcase works - Complex demos with all step types work correctly
- Novice-friendly - Someone unfamiliar with the codebase can create a demo by reading examples
- Shareable - Built demos work when opened as local files or hosted anywhere
- Live execution - Presenters can run real commands/APIs during demos
- Professional appearance - UI looks polished enough for customer demos
- Authentication: How to handle APIs that require auth tokens? Environment variables? Secure storage?
- Large responses: Should we truncate large API responses? Allow expandable sections?
- Error recovery: If a step fails mid-demo, how do we handle retry/skip/abort?
Resolved:
Conditional steps- Implemented viagotonavigationBranching- Implemented viachoiceson slide steps
DemoScript includes JSON Schema validation for demo files. The schema is at demo.schema.json in the project root.
To enable IDE autocomplete, add this comment at the top of your demo.yaml:
# yaml-language-server: $schema=../../demo.schema.jsonOr configure your IDE's YAML extension to associate .yaml files with the schema.
See the examples directory for complete demo implementations:
examples/hello-world/- Minimal 3-step introexamples/feature-showcase/- Comprehensive feature demoexamples/browser-demo/- Browser automation demo