
Tutorial Updates
- 93 installs
- 325 repo stars
- Updated August 2, 2026
- athola/claude-night-market
Orchestrate multi-component tutorials from.manifest.yaml with tape, Playwright, and static assets into combined GIF outputs.
About
Tutorial Updates centers on manifest parsing for tutorial orchestration in the Claude Night Market toolchain. Solo builders maintaining product walkthroughs can define YAML manifests that list tape recordings, Playwright browser specs, and static GIFs, then apply composition rules for padding, background color, and layout. The module documents required fields, relative source paths, and options overrides such as fps and width for terminal captures. It fits Prism’s Build docs shelf but spans into Launch when refreshing distribution assets. Agents use it when tutorial repos outgrow hand-managed asset lists and need a single schema for regeneration. It assumes familiarity with VHS tape files and optional Playwright setup; it does not replace your CI secrets or hosting for npm serve steps. Extend with your own validators once manifests grow beyond the documented component trio.
- Full .manifest.yaml schema: name, title, components, and optional combine rules
- Component types: tape (VHS terminal), playwright (browser capture), and static assets
- Combine layouts: vertical, horizontal, sequential, grid, and picture-in-picture
- Pre-run requires hooks for commands such as npm run serve on Playwright components
- Manifest-driven output paths for per-component and combined GIF generation
Tutorial Updates by the numbers
- 93 all-time installs (skills.sh)
- Ranked #658 of 1,879 Documentation skills by installs in the Skillselion catalog
- Security screen: MEDIUM risk (skills.sh audit)
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/athola/claude-night-market --skill tutorial-updatesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 93 |
|---|---|
| repo stars | ★ 325 |
| Security audit | 2 / 3 scanners passed |
| Last updated | August 2, 2026 |
| Repository | athola/claude-night-market ↗ |
What it does
Orchestrate multi-component tutorials from.manifest.yaml with tape, Playwright, and static assets into combined GIF outputs.
Files
Table of Contents
- Overview
- Command Options
- Required TodoWrite Items
- Phase 1: Discovery (`tutorial-updates:discovery`))
- Step 1.1: Locate Tutorial Assets
- Step 1.2: Parse Manifests
- Step 1.3: Handle Options
- Phase 1.5: Validation (`tutorial-updates:validation`))
- Step 1.5.1: VHS Syntax Validation
- Step 1.5.2: Extract and Validate CLI Commands
- Step 1.5.3: Verify Demo Data Exists
- Step 1.5.4: Test Commands Locally
- Validation Flags
- Validation Exit Criteria
- Phase 1.6: Binary Rebuild (`tutorial-updates:rebuild`))
- Step 1.6.1: Detect Build System
- Step 1.6.2: Check Binary Freshness
- Step 1.6.3: Rebuild Binary
- Step 1.6.4: Verify Binary Accessibility
- Rebuild Flags
- Rebuild Exit Criteria
- Phase 2: Recording (`tutorial-updates:recording`))
- Step 2.1: Process Tape Components
- Step 2.2: Process Browser Components
- Step 2.3: Handle Multi-Component Tutorials
- Phase 3: Generation (`tutorial-updates:generation`))
- Step 3.1: Parse Tape Annotations
- Step 3.2: Generate Dual-Tone Markdown
- Step 3.3: Generate README Demo Section
- Demos
- Quickstart
- Phase 4: Integration (`tutorial-updates:integration`))
- Step 4.1: Verify All Outputs
- Step 4.2: Update SUMMARY.md (Book))
- Step 4.3: Report Results
- Exit Criteria
- Error Handling
- Scaffold Mode
Tutorial Updates Skill
Orchestrate tutorial generation with GIF recordings from VHS tape files and Playwright browser specs.
When To Use
- Generating or updating user-facing tutorials
- Creating VHS and Playwright tutorial recordings
When NOT To Use
- Internal documentation without user-facing tutorials
- API reference docs - use scribe:doc-generator instead
Overview
This skill coordinates the complete tutorial generation pipeline:
1. Discover tape files and manifests in the project 2. Validate tape commands and check binary freshness 3. Rebuild binaries if stale so demos reflect latest code 4. Record terminal sessions using VHS (scry:vhs-recording) 5. Record browser sessions using Playwright (scry:browser-recording) 6. Generate optimized GIFs (scry:gif-generation) 7. Compose multi-component tutorials (scry:media-composition) 8. Generate dual-tone markdown for docs/ and book/
Command Options
/update-tutorial quickstart # Single tutorial by name
/update-tutorial sync mcp # Multiple tutorials
/update-tutorial --all # All tutorials with manifests
/update-tutorial --list # Show available tutorials
/update-tutorial --scaffold # Create structure without recordingVerification: Run the command with --help flag to verify availability.
Required TodoWrite Items
Create todos with these prefixes for progress tracking:
**Verification:** Run the command with `--help` flag to verify availability.
- tutorial-updates:discovery
- tutorial-updates:validation
- tutorial-updates:rebuild
- tutorial-updates:recording
- tutorial-updates:generation
- tutorial-updates:integrationVerification: Run the command with --help flag to verify availability.
Phase 1: Discovery (tutorial-updates:discovery)
Step 1.1: Locate Tutorial Assets
Find tape files and manifests in the project:
# Find manifest files
find . -name "*.manifest.yaml" -type f \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/node_modules/*" -not -path "*/.git/*" \
2>/dev/null | head -20
# Find tape files
find . -name "*.tape" -type f \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/node_modules/*" -not -path "*/.git/*" \
2>/dev/null | head -20
# Find browser specs
find . -name "*.spec.ts" -path "*/browser/*" -type f \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/node_modules/*" -not -path "*/.git/*" \
2>/dev/null | head -20Verification: Run the command with --help flag to verify availability.
Step 1.2: Parse Manifests
For each manifest file, extract:
- Tutorial name and title
- Component list (tape files, playwright specs)
- Output paths for GIFs
- Composition rules (layout, combine options)
See modules/manifest-parsing.md for manifest schema details.
Step 1.3: Handle Options
| Option | Behavior |
|---|---|
--list | Display discovered tutorials and exit |
--all | Process all discovered manifests |
--scaffold | Create directory structure and empty files without recording |
<names> | Process only specified tutorials |
When --list is specified:
**Verification:** Run the command with `--help` flag to verify availability.
Available tutorials:
quickstart assets/tapes/quickstart.tape
sync assets/tapes/sync.tape (manifest)
mcp assets/tapes/mcp.manifest.yaml (terminal + browser)
skill-debug assets/tapes/skill-debug.tapeVerification: Run the command with --help flag to verify availability.
Phase 1.5: Validation (tutorial-updates:validation)
CRITICAL: Validate tape commands BEFORE running VHS to avoid expensive regeneration cycles.
See modules/tape-validation.md for detailed validation logic.
Step 1.5.1: VHS Syntax Validation
Check each tape file for valid VHS syntax:
# Required: Output directive exists
grep -q '^Output ' "$tape_file" || echo "ERROR: Missing Output directive"
# Check for balanced quotes in Type directives
grep '^Type ' "$tape_file" | while read -r line; do
quote_count=$(echo "$line" | tr -cd '"' | wc -c)
if [ $((quote_count % 2)) -ne 0 ]; then
echo "ERROR: Unbalanced quotes: $line"
fi
doneVerification: Run the command with --help flag to verify availability.
Step 1.5.2: Extract and Validate CLI Commands
For each Type directive, extract the command and validate flags:
# Extract commands from Type directives
grep '^Type ' "$tape_file" | sed 's/^Type "//' | sed 's/"$//' | while read -r cmd; do
# Skip comments, clear, and echo commands
[[ "$cmd" =~ ^# ]] && continue
[[ "$cmd" == "clear" ]] && continue
# For skrills commands, validate flags exist
if [[ "$cmd" =~ ^skrills ]]; then
base_cmd=$(echo "$cmd" | awk '{print $1, $2}')
flags=$(echo "$cmd" | grep -oE '\-\-[a-zA-Z0-9-]+' || true)
for flag in $flags; do
if ! $base_cmd --help 2>&1 | grep -q -- "$flag"; then
echo "ERROR: Invalid flag '$flag' in command: $cmd"
fi
done
fi
doneVerification: Run the command with --help flag to verify availability.
Step 1.5.3: Verify Demo Data Exists
If the tape uses demo data, verify it exists and is populated:
# Check SKRILLS_SKILL_DIR if set
skill_dir=$(grep '^Env SKRILLS_SKILL_DIR' "$tape_file" | sed 's/.*"\(.*\)"/\1/')
if [ -n "$skill_dir" ]; then
if [ ! -d "$skill_dir" ]; then
echo "ERROR: Demo skill directory missing: $skill_dir"
else
skill_count=$(find "$skill_dir" -name "SKILL.md" 2>/dev/null | wc -l)
if [ "$skill_count" -eq 0 ]; then
echo "ERROR: No skills in demo directory: $skill_dir"
else
echo "OK: Found $skill_count demo skills in $skill_dir"
fi
fi
fiVerification: Run the command with --help flag to verify availability.
Step 1.5.4: Test Commands Locally
CRITICAL: Run each extracted command locally to verify it produces expected output:
# For each command in the tape, do a quick sanity check
# This catches issues like:
# - Commands that exit with non-zero status
# - Commands that produce no output (won't show anything in GIF)
# - Commands that require user input (will hang VHS)
for cmd in $(extract_commands "$tape_file"); do
# Run with timeout to catch hanging commands
if ! timeout 5s bash -c "$cmd" &>/dev/null; then
echo "WARNING: Command may fail or hang: $cmd"
fi
doneVerification: Run the command with --help flag to verify availability.
Validation Flags
| Flag | Behavior |
|---|---|
--validate-only | Run validation without generating GIF |
--skip-validation | Bypass validation for rapid regeneration |
Validation Exit Criteria
- [ ] VHS tape syntax is valid (Output directive, balanced quotes)
- [ ] All CLI flags in commands are valid (verified against --help)
- [ ] Demo data directories exist and are populated
- [ ] Commands execute successfully with expected output
If validation fails: Stop immediately, report errors, and do NOT proceed to VHS recording.
Phase 1.6: Binary Rebuild (tutorial-updates:rebuild)
CRITICAL: Ensure the binary being tested in tapes matches the latest source code. Stale binaries produce misleading demos.
Step 1.6.1: Detect Build System
Identify the project's build system:
# Check for Cargo (Rust)
if [ -f "Cargo.toml" ]; then
BUILD_SYSTEM="cargo"
BINARY_NAME=$(grep '^name = ' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
echo "Detected Cargo project: $BINARY_NAME"
# Check for Makefile
elif [ -f "Makefile" ]; then
BUILD_SYSTEM="make"
echo "Detected Make project"
# Unknown
else
echo "WARNING: Unknown build system, skipping binary check"
BUILD_SYSTEM="unknown"
fiVerification: Run make --dry-run to verify build configuration.
Step 1.6.2: Check Binary Freshness
Compare binary modification time against Git HEAD:
check_binary_freshness() {
local binary_name="$1"
# Locate binary (check cargo install location first, then PATH)
local binary_path=$(which "$binary_name" 2>/dev/null)
if [ -z "$binary_path" ]; then
echo "WARNING: Binary '$binary_name' not found in PATH"
return 1
fi
# Get binary modification time (Linux/macOS compatible)
local binary_mtime
if command -v stat >/dev/null 2>&1; then
# Linux
binary_mtime=$(stat -c %Y "$binary_path" 2>/dev/null || \
# macOS
stat -f %m "$binary_path" 2>/dev/null)
else
echo "WARNING: stat command not available, skipping freshness check"
return 2
fi
# Get Git HEAD commit time
local git_head_time=$(git log -1 --format=%ct 2>/dev/null)
if [ -z "$git_head_time" ]; then
echo "WARNING: Not a git repository, skipping freshness check"
return 2
fi
# Compare timestamps
if [ "$binary_mtime" -lt "$git_head_time" ]; then
echo "STALE: Binary is older than Git HEAD"
echo " Binary: $(date -d @$binary_mtime 2>/dev/null || date -r $binary_mtime)"
echo " HEAD: $(date -d @$git_head_time 2>/dev/null || date -r $git_head_time)"
return 1
else
echo "OK: Binary is up-to-date"
return 0
fi
}Verification: Run git status to confirm working tree state.
Step 1.6.3: Rebuild Binary
Rebuild using the detected build system:
rebuild_binary() {
local build_system="$1"
local binary_name="$2"
case "$build_system" in
cargo)
echo "Rebuilding with Cargo..."
# Use cargo install for CLI binaries
if [ -d "crates/cli" ]; then
cargo install --path crates/cli --locked --quiet
else
cargo install --path . --locked --quiet
fi
;;
make)
echo "Rebuilding with Make..."
make build --quiet
;;
*)
echo "ERROR: Cannot rebuild, unknown build system"
return 1
;;
esac
echo "Build complete: $binary_name"
}Verification: Run make --dry-run to verify build configuration.
Step 1.6.4: Verify Binary Accessibility
Ensure the rebuilt binary is accessible:
verify_binary() {
local binary_name="$1"
if ! command -v "$binary_name" >/dev/null 2>&1; then
echo "ERROR: Binary '$binary_name' not found after rebuild"
echo " Check PATH includes: $HOME/.cargo/bin"
return 1
fi
# Test binary can execute
if ! "$binary_name" --version >/dev/null 2>&1; then
echo "WARNING: Binary exists but --version failed"
else
echo "OK: Binary is accessible and functional"
"$binary_name" --version
fi
}Verification: Run pytest -v to verify tests pass.
Rebuild Flags
| Flag | Behavior |
|---|---|
--skip-rebuild | Skip binary freshness check and rebuild |
--force-rebuild | Force rebuild even if binary is fresh |
Rebuild Exit Criteria
- [ ] Build system detected (Cargo, Make, or explicitly skipped)
- [ ] Binary freshness checked against Git HEAD
- [ ] Binary rebuilt if stale (or forced)
- [ ] Rebuilt binary is accessible in PATH
- [ ] Binary executes successfully (--version test)
If rebuild fails: Stop immediately, report build errors, and do NOT proceed to tape validation or VHS recording.
Phase 2: Recording (tutorial-updates:recording)
Step 2.1: Process Tape Components
For each tape file component:
1. Parse tape file for metadata annotations (@step, @docs-brief, @book-detail) 2. Validate Output directive exists 3. Invoke Skill(scry:vhs-recording) with tape file path 4. Verify GIF output was created
Step 2.2: Process Browser Components
For each playwright spec component:
1. Check requires field for prerequisite commands (e.g., start server) 2. Launch any required background processes 3. Invoke Skill(scry:browser-recording) with spec path 4. Stop background processes 5. Invoke Skill(scry:gif-generation) to convert WebM to GIF
Step 2.3: Handle Multi-Component Tutorials
For manifests with combine section:
1. Verify all component GIFs exist 2. Invoke Skill(scry:media-composition) with manifest 3. Verify combined output was created
Phase 3: Generation (tutorial-updates:generation)
Step 3.1: Parse Tape Annotations
Extract documentation content from tape files:
# @step Install skrills
# @docs-brief Install via cargo
# @book-detail The recommended installation method uses cargo...
Type "cargo install skrills"Verification: Run the command with --help flag to verify availability.
Annotations:
@step- Step title/heading@docs-brief- Concise text for project docs (docs/ directory)@book-detail- Extended text for technical book (book/ directory)
Step 3.2: Generate Dual-Tone Markdown
Generate two versions of each tutorial:
1. Project docs (docs/tutorials/<name>.md)
- Brief, action-oriented
- Uses @docs-brief content
- Focuses on commands and quick results
2. Technical book (book/src/tutorials/<name>.md)
- Detailed, educational
- Uses @book-detail content
- Explains concepts and rationale
See modules/markdown-generation.md for formatting details.
Step 3.3: Generate README Demo Section
Create or update demo section in README.md:
## Demos
### Quickstart

*Install, validate, analyze, and serve in under a minute. [Full tutorial](docs/tutorials/quickstart.md)*Verification: Run the command with --help flag to verify availability.
Phase 4: Integration (tutorial-updates:integration)
Step 4.1: Verify All Outputs
Confirm all expected files exist:
# Check GIF files
for gif in assets/gifs/*.gif; do
if [[ -f "$gif" ]]; then
echo "OK: $gif ($(du -h "$gif" | cut -f1))"
else
echo "MISSING: $gif"
fi
done
# Check markdown files
ls -la docs/tutorials/*.md 2>/dev/null
ls -la book/src/tutorials/*.md 2>/dev/nullVerification: Run the command with --help flag to verify availability.
Step 4.2: Update SUMMARY.md (Book)
If the project has an mdBook structure, update book/src/SUMMARY.md:
- [Tutorials](./tutorials/README.md)
- [Quickstart](./tutorials/quickstart.md)
- [Sync Workflow](./tutorials/sync.md)
- [MCP Integration](./tutorials/mcp.md)
- [Skill Debugging](./tutorials/skill-debug.md)Verification: Run the command with --help flag to verify availability.
Step 4.3: Report Results
Summarize the update:
**Verification:** Run the command with `--help` flag to verify availability.
Tutorial Update Complete
========================
Tutorials processed: 4
GIFs generated: 5
- quickstart.gif (1.2MB)
- sync.gif (980KB)
- mcp-terminal.gif (1.5MB)
- mcp-browser.gif (2.1MB)
- skill-debug.gif (890KB)
Markdown generated:
- docs/tutorials/ (4 files)
- book/src/tutorials/ (4 files)
README demo section updatedVerification: Run the command with --help flag to verify availability.
Exit Criteria
- [ ] All specified tutorials processed (or all if --all)
- [ ] GIF files created at manifest-specified paths
- [ ] Dual-tone markdown generated for each tutorial
- [ ] README demo section updated with GIF embeds
- [ ] Book SUMMARY.md updated (if applicable)
- [ ] All TodoWrite items completed
Error Handling
| Error | Resolution |
|---|---|
| VHS not installed | go install github.com/charmbracelet/vhs@latest |
| Playwright not installed | npm install -D @playwright/test && npx playwright install chromium |
| Tape file missing Output | Add Output assets/gifs/<name>.gif directive |
| Browser spec requires server | Start server before running spec |
| GIF too large | Adjust fps/scale in gif-generation |
Scaffold Mode
When --scaffold is specified, create structure without recording:
1. Create assets/tapes/ directory 2. Create assets/gifs/ directory 3. Create assets/browser/ directory (if browser tutorials planned) 4. Create template tape file with metadata annotations 5. Create template manifest file 6. Create empty markdown files in docs/tutorials/ and book/src/tutorials/
Template tape file:
# @title: Tutorial Name
# @description: Brief description of the tutorial
Output assets/gifs/tutorial-name.gif
Set FontSize 14
Set Width 1200
Set Height 600
Set Theme "Catppuccin Mocha"
# @step Step 1 Title
# @docs-brief Brief docs text
# @book-detail Extended book text with more context and explanation
Type "command here"
Enter
Sleep 2sVerification: Run the command with --help flag to verify availability.
Manifest Parsing Module
Parse .manifest.yaml files and tape file annotations for tutorial orchestration.
Manifest Schema
Tutorial manifests define multi-component tutorials with composition rules:
# Full manifest schema
name: string # Required: identifier for the tutorial
title: string # Optional: human-readable title
description: string # Optional: brief description
components: # Required: list of media components
- type: tape # Component type: tape, playwright, static
source: path/to.tape # Path to source file (relative to manifest)
output: path/to.gif # Path for generated output
options: # Optional: component-specific options
fps: 10
width: 800
- type: playwright
source: browser/spec.ts
output: assets/gifs/browser.gif
requires: # Optional: commands to run before
- "npm run serve"
- type: static # Pre-existing asset (no generation)
source: existing.gif
output: existing.gif
combine: # Optional: composition rules
output: combined.gif # Path for combined output
layout: vertical # Layout: vertical, horizontal, sequential, grid, pip
options:
padding: 10
background: "#1a1a2e"Component Types
Tape Components
VHS tape files for terminal recordings:
- type: tape
source: quickstart.tape
output: assets/gifs/quickstart.gif
options:
# Override tape file settings if needed
width: 1000
height: 500Playwright Components
Browser automation specs:
- type: playwright
source: browser/mcp-dashboard.spec.ts
output: assets/gifs/mcp-browser.gif
requires:
- "skrills serve"
options:
fps: 12
width: 1280The requires array specifies commands to run before the spec. These run as background processes and are terminated after recording.
Static Components
Pre-existing assets that don't need generation:
- type: static
source: diagrams/architecture.gif
output: diagrams/architecture.gifParsing Tape File Annotations
Tape files contain inline annotations for documentation generation:
Annotation Format
# @title: Tutorial Title
# @description: Brief description for README
# @step Step Name
# @docs-brief Concise text for project docs
# @book-detail Extended explanation for technical book
Type "command"
EnterAnnotation Types
| Annotation | Scope | Purpose |
|---|---|---|
@title | File | Tutorial title |
@description | File | Brief description |
@step | Block | Step heading |
@docs-brief | Block | Concise docs text |
@book-detail | Block | Extended book text |
Parsing Algorithm
def parse_tape_annotations(tape_content: str) -> dict:
"""Parse tape file for documentation annotations."""
result = {
"title": None,
"description": None,
"steps": []
}
current_step = None
for line in tape_content.splitlines():
line = line.strip()
# File-level annotations
if line.startswith("# @title:"):
result["title"] = line.split(":", 1)[1].strip()
elif line.startswith("# @description:"):
result["description"] = line.split(":", 1)[1].strip()
# Step-level annotations
elif line.startswith("# @step"):
# Save previous step
if current_step:
result["steps"].append(current_step)
# Start new step
step_name = line.replace("# @step", "").strip()
current_step = {
"name": step_name,
"docs_brief": None,
"book_detail": None,
"commands": []
}
elif line.startswith("# @docs-brief"):
if current_step:
current_step["docs_brief"] = line.replace("# @docs-brief", "").strip()
elif line.startswith("# @book-detail"):
if current_step:
current_step["book_detail"] = line.replace("# @book-detail", "").strip()
# Command lines (Type, Enter, etc.)
elif current_step and line.startswith("Type"):
# Extract command text
match = re.match(r'Type(?:@\d+ms)?\s+"(.+)"', line)
if match:
current_step["commands"].append(match.group(1))
# Don't forget last step
if current_step:
result["steps"].append(current_step)
return resultManifest Validation
Required Fields
# Validate manifest has required fields
yq eval '.name' manifest.yaml >/dev/null || echo "ERROR: missing name"
yq eval '.components | length > 0' manifest.yaml | grep -q true || echo "ERROR: no components"
# Validate each component
for i in $(seq 0 $(($(yq eval '.components | length' manifest.yaml) - 1))); do
yq eval ".components[$i].type" manifest.yaml >/dev/null || echo "ERROR: component $i missing type"
yq eval ".components[$i].source" manifest.yaml >/dev/null || echo "ERROR: component $i missing source"
yq eval ".components[$i].output" manifest.yaml >/dev/null || echo "ERROR: component $i missing output"
doneSource File Validation
# Check all source files exist
for source in $(yq eval '.components[].source' manifest.yaml); do
if [[ ! -f "$source" ]]; then
echo "ERROR: Source file not found: $source"
fi
doneDiscovery Patterns
Find All Manifests
# Find manifest files in common locations
find . -name "*.manifest.yaml" -type f \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/node_modules/*" -not -path "*/.git/*" \
2>/dev/null
find assets -name "*.manifest.yaml" -type f 2>/dev/null
find tutorials -name "*.manifest.yaml" -type f 2>/dev/nullFind Standalone Tape Files
Tape files without manifests (single-component tutorials):
# Find tape files
find . -name "*.tape" -type f \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/node_modules/*" -not -path "*/.git/*" \
2>/dev/null
# Filter out those with manifests
for tape in $(find . -name "*.tape" -type f \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/node_modules/*" -not -path "*/.git/*" \
2>/dev/null); do
manifest="${tape%.tape}.manifest.yaml"
if [[ ! -f "$manifest" ]]; then
echo "Standalone: $tape"
fi
doneBuild Tutorial Index
# Create index of all tutorials
echo "Tutorials:"
echo "=========="
# From manifests
for manifest in $(find . -name "*.manifest.yaml" -type f \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/node_modules/*" -not -path "*/.git/*" \
2>/dev/null); do
name=$(yq eval '.name' "$manifest")
title=$(yq eval '.title // .name' "$manifest")
components=$(yq eval '.components | length' "$manifest")
echo " $name: $title ($components components) [manifest]"
done
# Standalone tapes
for tape in $(find . -name "*.tape" -type f \
-not -path "*/.venv/*" -not -path "*/__pycache__/*" \
-not -path "*/node_modules/*" -not -path "*/.git/*" \
2>/dev/null); do
manifest="${tape%.tape}.manifest.yaml"
if [[ ! -f "$manifest" ]]; then
name=$(basename "$tape" .tape)
echo " $name: $tape [standalone]"
fi
doneError Handling
| Error | Resolution |
|---|---|
| Manifest parse error | Validate YAML syntax with yq eval '.' manifest.yaml |
| Missing source file | Check path is relative to manifest location |
| Unknown component type | Use tape, playwright, or static |
| Missing combine output | Add combine.output field if combine section exists |
| Circular requires | validate background processes don't depend on each other |
Example Manifests
Simple Tape Tutorial
name: quickstart
title: "Quickstart Guide"
components:
- type: tape
source: quickstart.tape
output: assets/gifs/quickstart.gifMulti-Component Tutorial
name: mcp
title: "MCP Server Integration"
description: "Terminal and browser demo of MCP server"
components:
- type: tape
source: mcp-terminal.tape
output: assets/gifs/mcp-terminal.gif
- type: playwright
source: browser/mcp-dashboard.spec.ts
output: assets/gifs/mcp-browser.gif
requires:
- "skrills serve"
combine:
output: assets/gifs/mcp-combined.gif
layout: vertical
options:
padding: 10
background: "#0d1117"Tutorial with Static Assets
name: architecture
title: "Architecture Overview"
components:
- type: tape
source: arch-demo.tape
output: assets/gifs/arch-demo.gif
- type: static
source: diagrams/system-overview.gif
output: diagrams/system-overview.gif
combine:
output: assets/gifs/architecture-full.gif
layout: sequentialMarkdown Generation Module
Generate dual-tone markdown documentation from tape file annotations and manifest metadata.
Dual-Tone System
Tutorials are generated in two tones for different audiences:
| Tone | Location | Audience | Style |
|---|---|---|---|
| Project Docs | docs/tutorials/ | Users getting started | Concise, action-oriented |
| Technical Book | book/src/tutorials/ | Developers learning deeply | Detailed, educational |
Annotation Sources
Content comes from tape file annotations:
# @step Install the CLI
# @docs-brief Install via cargo with a single command
# @book-detail The recommended installation method uses cargo, Rust's package manager. This validates you get the latest stable release with all dependencies properly resolved. For development builds or specific versions, you can also install from source.
Type "cargo install skrills"@docs-brief- Used for project docs (brief, focused)@book-detail- Used for technical book (extended, contextual)- If only one is present, use it for both
- If neither is present, generate minimal text from step name
Project Docs Format
Template Structure
# {Tutorial Title}
{Description from @description}

## Prerequisites
- Prerequisite 1
- Prerequisite 2
## Steps
### {Step 1 Name}
{@docs-brief content}
{command from Type directive}
### {Step 2 Name}
{@docs-brief content}
{command}
## Next Steps
- Link to related tutorial 1
- Link to related tutorial 2Example Output
# Quickstart
Install, validate, analyze, and serve in under a minute.

## Prerequisites
- Rust toolchain installed (`rustup`)
- Terminal with UTF-8 support
## Steps
### Install skrills
Install via cargo with a single command.
cargo install skrills
### Validate Skills
Validate and auto-fix missing frontmatter.
skrills validate --target codex --autofix
### Analyze Token Usage
Analyze skills for token optimization opportunities.
skrills analyze --min-tokens 500 --suggestions
### Start MCP Server
Start the MCP server.
skrills serve
## Next Steps
- [Sync Workflow](./sync.md) - Bidirectional sync between Claude Code and Codex CLI
- [MCP Integration](./mcp.md) - Use skrills as an MCP serverTechnical Book Format
Template Structure
# {Tutorial Title}
{Extended description}
## Overview
{Context and learning objectives}

## {Step 1 Name}
{@book-detail content - multiple paragraphs allowed}
{command from Type directive}
{Additional explanation of what the command does}
## {Step 2 Name}
{@book-detail content}
{command}
{Explanation of output and next steps}
## Summary
{Key takeaways}
## Further Reading
- Internal link 1
- External reference 1Example Output
# Quickstart
This guide walks through the complete skrills workflow: installation, validation, analysis, and serving skills via MCP.
## Overview
What this guide covers:
- How to install skrills using cargo
- The difference between Claude Code and Codex CLI validation targets
- How to analyze skills for token optimization
- How to expose skills via the MCP protocol

## Install skrills
The recommended installation method uses cargo, Rust's package manager. This validates you get the latest stable release with all dependencies properly resolved. For development builds or specific versions, you can also install from source.
cargo install skrills
The binary will be placed in `~/.cargo/bin/`, which should be in your PATH if you installed Rust using rustup.
## Validate Skills
Skrills validates skills against two targets with different strictness levels. Claude Code accepts any markdown file as a skill, while Codex CLI requires YAML frontmatter with specific fields.
skrills validate --target codex --autofix
The `--autofix` flag automatically derives missing frontmatter from the file path and content:
1. Parses the skill filename to derive `name`
2. Extracts the first paragraph as `description`
3. Inserts YAML frontmatter at the file start
This makes migration from Claude Code to Codex straightforward.
## Analyze Token Usage
The analyzer reports token counts for each skill and suggests optimizations for large skills that may consume excessive context.
skrills analyze --min-tokens 500 --suggestions
Skills exceeding the threshold are flagged with specific recommendations:
- Split into multiple focused skills
- Extract reusable modules
- Remove redundant content
## Start MCP Server
When running as an MCP server, skrills exposes tools for skill discovery, validation, and analysis to any MCP-compatible client.
skrills serve
The server listens on the default MCP port and responds to tool invocations from Claude Code or other clients.
## Summary
- Install skrills with `cargo install skrills`
- Validate skills for Codex compatibility with `--target codex`
- Use `--autofix` to automatically add required frontmatter
- Analyze token usage to optimize context consumption
- Serve skills via MCP for integration with AI assistants
## Further Reading
- [Sync Workflow](./sync.md) - Bidirectional synchronization between skill repositories
- [MCP Integration](./mcp.md) - Advanced MCP server configuration
- [Skill Debugging](./skill-debug.md) - Troubleshooting skill loading issuesGIF Embedding
Relative Path Calculation
GIFs are embedded with paths relative to the markdown file:
| Markdown Location | GIF Location | Relative Path |
|---|---|---|
docs/tutorials/quickstart.md | assets/gifs/quickstart.gif | ../../assets/gifs/quickstart.gif |
book/src/tutorials/quickstart.md | assets/gifs/quickstart.gif | ../../../assets/gifs/quickstart.gif |
README.md | assets/gifs/quickstart.gif | assets/gifs/quickstart.gif |
Path Calculation Algorithm
def relative_gif_path(markdown_path: str, gif_path: str) -> str:
"""Calculate relative path from markdown file to GIF."""
from pathlib import Path
md = Path(markdown_path)
gif = Path(gif_path)
# Get common ancestor
common = Path(*os.path.commonprefix([md.parts, gif.parts]))
# Calculate relative path
md_depth = len(md.parent.relative_to(common).parts)
gif_relative = gif.relative_to(common)
return "../" * md_depth + str(gif_relative)Embedding Format
For multi-component tutorials with combined GIF:

*This demo shows both terminal and browser interactions.*README Integration
Demo Section Template
## Demos
### {Tutorial 1 Title}

*{Description}. [Full tutorial]({docs-tutorial-path})*
### {Tutorial 2 Title}

*{Description}. [Full tutorial]({docs-tutorial-path})*Generating README Section
def generate_readme_demos(tutorials: list) -> str:
"""Generate demo section for README."""
lines = ["## Demos", ""]
for tutorial in tutorials:
lines.extend([
f"### {tutorial['title']}",
f"![{tutorial['title']} demo]({tutorial['gif_path']})",
f"*{tutorial['description']}. [Full tutorial]({tutorial['docs_path']})*",
""
])
return "\n".join(lines)Updating README
Replace the existing demo section or append if not present:
# Check if demo section exists
if grep -q "^## Demos" README.md; then
# Replace section (between ## Demos and next ##)
sed -i '/^## Demos/,/^## [^D]/{ /^## [^D]/!d }' README.md
# Insert new content after ## Demos
fiBook SUMMARY.md Integration
Template
- [Tutorials](./tutorials/README.md)
- [{Tutorial 1 Title}](./tutorials/{name1}.md)
- [{Tutorial 2 Title}](./tutorials/{name2}.md)Detection and Update
# Check if tutorials section exists in SUMMARY.md
if [[ -f "book/src/SUMMARY.md" ]]; then
if grep -q "Tutorials" book/src/SUMMARY.md; then
echo "Tutorials section exists - update entries"
else
echo "Add Tutorials section to SUMMARY.md"
fi
fiContent Guidelines
Project Docs Style
- Action-oriented imperatives: "Install", "Run", "Configure"
- One paragraph per step maximum
- Focus on commands and results
- Minimal explanation of "why"
- Include prerequisites section
Technical Book Style
- Educational tone: "This guide explains..."
- Multiple paragraphs allowed per step
- Explain rationale and context
- Include troubleshooting tips
- Reference related concepts
- Add summary and further reading sections
Common Rules (Both)
- No filler phrases ("simply", "just", "easily")
- No emojis or decorative elements
- Grounded, specific language
- Code blocks for all commands
- Consistent heading hierarchy
- Prose text wraps at 80 chars per line (hybrid wrapping:
prefer sentence/clause boundaries over arbitrary breaks)
- Blank line before and after every heading
- ATX headings only (
#prefix, no setext underlines) - Blank line before every list
- Reference-style links when inline links push past 80 chars
- Full formatting spec:
Skill(leyline:markdown-formatting)
Error Handling
| Issue | Resolution |
|---|---|
| Missing @docs-brief | Fall back to @book-detail or step name |
| Missing @book-detail | Fall back to @docs-brief or generate minimal text |
| No Output directive | Skip GIF embed, log warning |
| Invalid path calculation | Verify markdown and GIF paths are relative to project root |
| README section conflict | Preserve user content outside ## Demos section |
Tape Validation Module
Pre-flight validation for VHS tape files before GIF generation. Validate commands work correctly and demo data exists BEFORE running the time-consuming VHS recording.
Overview
This module catches errors early:
- Stale binaries detected before recording (Phase 0)
- Invalid CLI flags discovered before GIF generation
- Missing demo data detected before recording starts
- VHS syntax issues reported before execution
Validation Phases
Phase 0: Binary Freshness Check
CRITICAL: Verify the CLI binary matches the latest source code. Stale binaries produce misleading demos.
# Check if binary is older than Git HEAD
check_binary_freshness() {
local binary_name="$1"
# Locate binary in PATH
local binary_path=$(which "$binary_name" 2>/dev/null)
if [ -z "$binary_path" ]; then
echo "WARNING: Binary '$binary_name' not found in PATH"
return 1
fi
# Get binary modification time (Linux/macOS compatible)
local binary_mtime
binary_mtime=$(stat -c %Y "$binary_path" 2>/dev/null || \
stat -f %m "$binary_path" 2>/dev/null)
# Get Git HEAD commit time
local git_head_time=$(git log -1 --format=%ct 2>/dev/null)
if [ -z "$git_head_time" ]; then
echo "WARNING: Not a git repository, skipping freshness check"
return 2
fi
# Compare timestamps
if [ "$binary_mtime" -lt "$git_head_time" ]; then
echo "STALE: Binary is older than Git HEAD"
echo " Binary: $(date -d @$binary_mtime 2>/dev/null || date -r $binary_mtime)"
echo " HEAD: $(date -d @$git_head_time 2>/dev/null || date -r $git_head_time)"
echo ""
echo "RECOMMENDATION: Rebuild binary before recording tape"
echo " cargo install --path crates/cli --locked"
return 1
else
echo "OK: Binary is up-to-date with Git HEAD"
return 0
fi
}Error handling:
- Exit code 0: Binary is fresh
- Exit code 1: Binary is stale (older than HEAD)
- Exit code 2: Cannot determine (not in git repo or binary not found)
Integration:
# Run before tape validation
check_binary_freshness "skrills"
if [ $? -eq 1 ]; then
echo "ERROR: Stale binary detected"
echo "Run 'cargo install --path crates/cli --locked' to rebuild"
exit 1
fiCommand Validation Phases
1. VHS Syntax Validation
Check that the tape file has valid VHS syntax:
# Required: Output directive
grep -q '^Output ' "$tape_file" || echo "ERROR: Missing Output directive"
# Check for balanced quotes in Type directives
grep '^Type ' "$tape_file" | while read -r line; do
# Count quotes (should be even)
quote_count=$(echo "$line" | tr -cd '"' | wc -c)
if [ $((quote_count % 2)) -ne 0 ]; then
echo "ERROR: Unbalanced quotes in: $line"
fi
doneError examples:
- "Missing Output directive in quickstart.tape:1"
- "Unbalanced quotes in Type directive at quickstart.tape:15"
2. Command Extraction
Parse Type directives to extract shell commands:
# Extract commands from Type directives
extract_commands() {
local tape_file="$1"
grep '^Type ' "$tape_file" | \
sed 's/^Type "//' | \
sed 's/"$//' | \
grep -v '^#' | \
grep -v '^clear$' | \
grep -v '^$'
}Example:
Type "skrills validate --errors-only"Extracts: skrills validate --errors-only
3. CLI Flag Validation
For each extracted command, validate flags exist:
validate_command_flags() {
local cmd="$1"
local line_num="$2"
# Extract the base command (e.g., "skrills validate")
local base_cmd=$(echo "$cmd" | awk '{print $1, $2}')
# Get help output for flag discovery
local help_output=$($base_cmd --help 2>&1)
# Extract flags from the command
local flags=$(echo "$cmd" | grep -oE '\-\-[a-zA-Z0-9-]+')
for flag in $flags; do
if ! echo "$help_output" | grep -q -- "$flag"; then
echo "ERROR: Invalid flag '$flag' at line $line_num"
echo " Command: $cmd"
echo " Run '$base_cmd --help' to see available flags"
fi
done
}Example validation:
# Command from tape line 12
skrills create demo --sample basic
# Validation
$ skrills create --help | grep -- '--sample'
# (no output - flag doesn't exist)
# Error output
ERROR: Invalid flag '--sample' at line 12
Command: skrills create demo --sample basic
Run 'skrills create --help' to see available flags4. Demo Data Verification
Check that demo directories and skills exist:
verify_demo_data() {
local tape_file="$1"
# Extract Env SKRILLS_SKILL_DIR if set
local skill_dir=$(grep '^Env SKRILLS_SKILL_DIR' "$tape_file" | \
sed 's/.*"\(.*\)"/\1/')
if [ -n "$skill_dir" ]; then
if [ ! -d "$skill_dir" ]; then
echo "ERROR: Demo skill directory does not exist: $skill_dir"
return 1
fi
# Check it has content
local skill_count=$(find "$skill_dir" -name "SKILL.md" 2>/dev/null | wc -l)
if [ "$skill_count" -eq 0 ]; then
echo "ERROR: Demo skill directory is empty: $skill_dir"
echo " Expected at least one SKILL.md file"
return 1
fi
echo "OK: Demo skills found: $skill_count skills in $skill_dir"
fi
# Check for other referenced directories
grep '^Type.*mkdir\|^Type.*cp\|^Type.*cd' "$tape_file" | while read -r line; do
# Extract directory paths and verify parent exists
# This is optional - catches obvious path errors
true
done
}5. Expected Output Verification
For commands that should produce visible output, verify they will:
verify_expected_output() {
local cmd="$1"
# Skip echo commands (always produce output)
echo "$cmd" | grep -q '^echo ' && return 0
# For skrills commands, do a dry-run to verify output
if echo "$cmd" | grep -q '^skrills '; then
# Run with --help to verify command exists
local base=$(echo "$cmd" | awk '{print $1, $2}')
if ! $base --help &>/dev/null; then
echo "WARNING: Command may not produce output: $cmd"
fi
fi
}Complete Validation Script
#!/bin/bash
# validate_tape.sh - Pre-flight validation for VHS tape files
validate_tape() {
local tape_file="$1"
local errors=0
echo "=== Validating: $tape_file ==="
# Phase 1: Syntax
echo "Phase 1: VHS Syntax..."
if ! grep -q '^Output ' "$tape_file"; then
echo " ERROR: Missing Output directive"
((errors++))
else
echo " OK: Output directive found"
fi
# Phase 2: Extract commands
echo "Phase 2: Extracting commands..."
local line_num=0
local cmd_count=0
while IFS= read -r line; do
((line_num++))
if [[ "$line" =~ ^Type\ \" ]]; then
cmd=$(echo "$line" | sed 's/^Type "//' | sed 's/"$//')
((cmd_count++))
# Phase 3: Validate CLI flags
if [[ "$cmd" =~ ^skrills ]]; then
base_cmd=$(echo "$cmd" | awk '{print $1, $2}')
flags=$(echo "$cmd" | grep -oE '\-\-[a-zA-Z0-9-]+' || true)
if [ -n "$flags" ]; then
help_output=$($base_cmd --help 2>&1 || echo "")
for flag in $flags; do
if ! echo "$help_output" | grep -q -- "$flag"; then
echo " ERROR: Invalid flag '$flag' at line $line_num"
echo " Command: $cmd"
((errors++))
fi
done
fi
fi
fi
done < "$tape_file"
echo " Extracted $cmd_count commands"
# Phase 4: Demo data
echo "Phase 4: Demo data verification..."
skill_dir=$(grep '^Env SKRILLS_SKILL_DIR' "$tape_file" | sed 's/.*"\(.*\)"/\1/' || true)
if [ -n "$skill_dir" ]; then
if [ ! -d "$skill_dir" ]; then
echo " ERROR: Demo skill directory missing: $skill_dir"
((errors++))
else
skill_count=$(find "$skill_dir" -name "SKILL.md" 2>/dev/null | wc -l)
if [ "$skill_count" -eq 0 ]; then
echo " ERROR: No skills in demo directory: $skill_dir"
((errors++))
else
echo " OK: Found $skill_count demo skills"
fi
fi
fi
# Summary
echo "=== Validation Complete ==="
if [ "$errors" -eq 0 ]; then
echo "PASSED: No errors found"
return 0
else
echo "FAILED: $errors error(s) found"
return 1
fi
}
# Run if called directly
if [ -n "$1" ]; then
validate_tape "$1"
fiIntegration with Workflow
Before running VHS, execute validation:
# In the tutorial-updates skill Phase 1.5
validate_tape "$tape_file"
if [ $? -ne 0 ]; then
echo "Validation failed. Fix errors before running VHS."
exit 1
fi
# Proceed to VHS recording
vhs "$tape_file"Flags
--validate-only
Run validation without generating GIF:
# Validate all tapes without recording
for tape in assets/tapes/*.tape; do
validate_tape "$tape"
done--skip-validation
Bypass validation for rapid regeneration:
# Skip validation when commands are known-good
if [ "$SKIP_VALIDATION" != "true" ]; then
validate_tape "$tape_file" || exit 1
fi
vhs "$tape_file"Exit Codes
| Code | Meaning |
|---|---|
| 0 | Validation passed |
| 1 | Validation failed (errors found) |
| 2 | Validation skipped (--skip-validation) |
Error Message Format
Standardized format for actionable errors:
ERROR: <short-description> at line <number>
Command: <full-command>
<hint-or-available-options>Example:
ERROR: Invalid flag '--sample' at line 12
Command: skrills validate --sample 5
Run 'skrills validate --help' to see available flagsRelated skills
FAQ
Is Tutorial Updates safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.