{"id":"a4393c9f-5cff-4f5b-a4cd-1d186f8b47a5","entityType":"agent","slug":"clawhub-athola-nm-sanctum-tutorial-updates","name":"tutorial-updates","canonicalUrl":"https://www.xpersona.co/agent/clawhub-athola-nm-sanctum-tutorial-updates","canonicalPath":"/agent/clawhub-athola-nm-sanctum-tutorial-updates","generatedAt":"2026-10-10T10:43:56.633Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-10T08:08:54.633Z","emptyReason":null},"description":"Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording Skill: tutorial-updates Owner: athola Summary: Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording Tags: latest:1.9.19 Version history: v1.9.19 | 2026-08-26T13:21:17.158Z | user Release v1.9.19 v1.9.17 | 2026-07-30T05:41:17.600Z | user Release v1.9.17 v1.9.16 | 2026-07-14T19:58:05.764Z | user Release v1.9.16 v1.9.14 | 2026-06-30T18:05:52.000Z | user Release v1.","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.6K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17emme0e2m3cpf7k2jvp3a84984b8z9:nm-sanctum-tutorial-updates","sourceUrl":"https://clawhub.ai/athola/nm-sanctum-tutorial-updates","homepage":"https://clawhub.ai/athola/skills/nm-sanctum-tutorial-updates","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/athola/nm-sanctum-tutorial-updates","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/athola/skills/nm-sanctum-tutorial-updates","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":64,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording Skill: tutorial-updates Owner: athola Summary: Gene"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T08:08:54.633Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T08:08:54.633Z","emptyReason":null},"stars":null,"forks":null,"downloads":1568,"packageName":null,"latestVersion":"1.9.19","tractionLabel":"1.6K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T08:08:54.632Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T08:08:54.633Z","lastCrawledAt":"2026-10-10T08:08:54.632Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T08:08:54.632Z","lastVerifiedAt":null,"highlights":[{"version":"1.9.19","createdAt":"2026-08-26T13:21:17.158Z","changelog":"Release v1.9.19","fileCount":6,"zipByteSize":18227},{"version":"1.9.17","createdAt":"2026-07-30T05:41:17.600Z","changelog":"Release v1.9.17","fileCount":6,"zipByteSize":18354},{"version":"1.9.16","createdAt":"2026-07-14T19:58:05.764Z","changelog":"Release v1.9.16","fileCount":6,"zipByteSize":18321},{"version":"1.9.14","createdAt":"2026-06-30T18:05:52.000Z","changelog":"Release v1.9.14","fileCount":6,"zipByteSize":18357},{"version":"1.9.13","createdAt":"2026-06-27T16:23:41.941Z","changelog":"Release v1.9.13","fileCount":6,"zipByteSize":18187},{"version":"1.9.12","createdAt":"2026-06-19T03:19:14.789Z","changelog":"Release v1.9.12","fileCount":6,"zipByteSize":18273},{"version":"1.0.3","createdAt":"2026-06-18T15:21:43.252Z","changelog":"Release v1.9.12","fileCount":6,"zipByteSize":18318},{"version":"1.0.2","createdAt":"2026-05-09T02:20:10.007Z","changelog":"Release v1.9.5","fileCount":6,"zipByteSize":18555}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17emme0e2m3cpf7k2jvp3a84984b8z9:nm-sanctum-tutorial-updates","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T10:43:56.632Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-athola-nm-sanctum-tutorial-updates/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-10T08:08:54.633Z","emptyReason":null},"readme":"Skill: tutorial-updates\n\nOwner: athola\n\nSummary: Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording\n\nTags: latest:1.9.19\n\nVersion history:\n\nv1.9.19 | 2026-08-26T13:21:17.158Z | user\n\nRelease v1.9.19\n\nv1.9.17 | 2026-07-30T05:41:17.600Z | user\n\nRelease v1.9.17\n\nv1.9.16 | 2026-07-14T19:58:05.764Z | user\n\nRelease v1.9.16\n\nv1.9.14 | 2026-06-30T18:05:52.000Z | user\n\nRelease v1.9.14\n\nv1.9.13 | 2026-06-27T16:23:41.941Z | user\n\nRelease v1.9.13\n\nv1.9.12 | 2026-06-19T03:19:14.789Z | user\n\nRelease v1.9.12\n\nv1.0.3 | 2026-06-18T15:21:43.252Z | user\n\nRelease v1.9.12\n\nv1.0.2 | 2026-05-09T02:20:10.007Z | user\n\nRelease v1.9.5\n\nv1.0.1 | 2026-05-06T14:21:35.698Z | user\n\nRelease v1.9.4\n\nv1.0.0 | 2026-04-20T12:01:41.671Z | auto\n\nInitial release of the \"tutorial-updates\" skill for orchestrating tutorial generation.\n\n- Automates discovery, validation, recording, and generation of user-facing tutorials using VHS tapes and Playwright specs.\n- Supports multi-step validation and binary rebuild before recording demos.\n- Generates dual-tone markdown and optimized GIFs for documentation.\n- Command options for updating, listing, or scaffolding tutorials.\n- Integrates with other \"night-market.sanctum\" and \"scry\" components for media processing.\n\nArchive index:\n\nArchive v1.9.19: 6 files, 18227 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10011b), modules/tape-validation.md (9405b), skill-card.md (2117b), SKILL.md (20046b), _meta.json (147b)\n\nFile v1.9.19:SKILL.md\n\n---\nname: tutorial-updates\ndescription: |\n  Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording\nversion: 1.9.8\ntriggers:\n  - tutorial\n  - gif\n  - vhs\n  - playwright\n  - documentation\n  - demo\n  - tutorial assets need refreshing\nmetadata: {\"openclaw\": {\"homepage\": \"https://github.com/athola/claude-night-market/tree/master/plugins/sanctum\", \"emoji\": \"\\ud83e\\udd9e\", \"requires\": {\"config\": [\"night-market.sanctum:shared\", \"night-market.sanctum:git-workspace-review\", \"night-market.scry:vhs-recording\", \"night-market.scry:browser-recording\", \"night-market.scry:gif-generation\", \"night-market.scry:media-composition\"]}}}\nsource: claude-night-market\nsource_plugin: sanctum\n---\n\n> **Night Market Skill** — ported from [claude-night-market/sanctum](https://github.com/athola/claude-night-market/tree/master/plugins/sanctum). For the full experience with agents, hooks, and commands, install the Claude Code plugin.\n\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Command Options](#command-options)\n- [Required TodoWrite Items](#required-todowrite-items)\n- [Phase 1: Discovery (`tutorial-updates:discovery`)](#phase-1:-discovery-(tutorial-updates:discovery))\n- [Step 1.1: Locate Tutorial Assets](#step-11:-locate-tutorial-assets)\n- [Step 1.2: Parse Manifests](#step-12:-parse-manifests)\n- [Step 1.3: Handle Options](#step-13:-handle-options)\n- [Phase 1.5: Validation (`tutorial-updates:validation`)](#phase-15:-validation-(tutorial-updates:validation))\n- [Step 1.5.1: VHS Syntax Validation](#step-151:-vhs-syntax-validation)\n- [Step 1.5.2: Extract and Validate CLI Commands](#step-152:-extract-and-validate-cli-commands)\n- [Step 1.5.3: Verify Demo Data Exists](#step-153:-verify-demo-data-exists)\n- [Step 1.5.4: Test Commands Locally](#step-154:-test-commands-locally)\n- [Validation Flags](#validation-flags)\n- [Validation Exit Criteria](#validation-exit-criteria)\n- [Phase 1.6: Binary Rebuild (`tutorial-updates:rebuild`)](#phase-16:-binary-rebuild-(tutorial-updates:rebuild))\n- [Step 1.6.1: Detect Build System](#step-161:-detect-build-system)\n- [Step 1.6.2: Check Binary Freshness](#step-162:-check-binary-freshness)\n- [Step 1.6.3: Rebuild Binary](#step-163:-rebuild-binary)\n- [Step 1.6.4: Verify Binary Accessibility](#step-164:-verify-binary-accessibility)\n- [Rebuild Flags](#rebuild-flags)\n- [Rebuild Exit Criteria](#rebuild-exit-criteria)\n- [Phase 2: Recording (`tutorial-updates:recording`)](#phase-2:-recording-(tutorial-updates:recording))\n- [Step 2.1: Process Tape Components](#step-21:-process-tape-components)\n- [Step 2.2: Process Browser Components](#step-22:-process-browser-components)\n- [Step 2.3: Handle Multi-Component Tutorials](#step-23:-handle-multi-component-tutorials)\n- [Phase 3: Generation (`tutorial-updates:generation`)](#phase-3:-generation-(tutorial-updates:generation))\n- [Step 3.1: Parse Tape Annotations](#step-31:-parse-tape-annotations)\n- [Step 3.2: Generate Dual-Tone Markdown](#step-32:-generate-dual-tone-markdown)\n- [Step 3.3: Generate README Demo Section](#step-33:-generate-readme-demo-section)\n- [Demos](#demos)\n- [Quickstart](#quickstart)\n- [Phase 4: Integration (`tutorial-updates:integration`)](#phase-4:-integration-(tutorial-updates:integration))\n- [Step 4.1: Verify All Outputs](#step-41:-verify-all-outputs)\n- [Step 4.2: Update SUMMARY.md (Book)](#step-42:-update-summarymd-(book))\n- [Step 4.3: Report Results](#step-43:-report-results)\n- [Exit Criteria](#exit-criteria)\n- [Error Handling](#error-handling)\n- [Scaffold Mode](#scaffold-mode)\n\n\n# Tutorial Updates Skill\n\nOrchestrate tutorial generation with GIF recordings from VHS tape files and Playwright browser specs.\n\n\n## When To Use\n\n- Generating or updating user-facing tutorials\n- Creating VHS and Playwright tutorial recordings\n\n## When NOT To Use\n\n- Internal documentation without user-facing tutorials\n- API reference docs - use scribe:doc-generator instead\n\n## Overview\n\nThis skill coordinates the complete tutorial generation pipeline:\n\n1. Discover tape files and manifests in the project\n2. Validate tape commands and check binary freshness\n3. Rebuild binaries if stale so demos reflect latest code\n4. Record terminal sessions using VHS (scry:vhs-recording)\n5. Record browser sessions using Playwright (scry:browser-recording)\n6. Generate optimized GIFs (scry:gif-generation)\n7. Compose multi-component tutorials (scry:media-composition)\n8. Generate dual-tone markdown for docs/ and book/\n\n## Command Options\n\n```bash\n/update-tutorial quickstart        # Single tutorial by name\n/update-tutorial sync mcp          # Multiple tutorials\n/update-tutorial --all             # All tutorials with manifests\n/update-tutorial --list            # Show available tutorials\n/update-tutorial --scaffold        # Create structure without recording\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Required TodoWrite Items\n\nCreate todos with these prefixes for progress tracking:\n\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n- tutorial-updates:discovery\n- tutorial-updates:validation\n- tutorial-updates:rebuild\n- tutorial-updates:recording\n- tutorial-updates:generation\n- tutorial-updates:integration\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 1: Discovery (`tutorial-updates:discovery`)\n\n### Step 1.1: Locate Tutorial Assets\n\nFind tape files and manifests in the project:\n\n```bash\n# Find manifest files\nfind . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n\n# Find tape files\nfind . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n\n# Find browser specs\nfind . -name \"*.spec.ts\" -path \"*/browser/*\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.2: Parse Manifests\n\nFor each manifest file, extract:\n- Tutorial name and title\n- Component list (tape files, playwright specs)\n- Output paths for GIFs\n- Composition rules (layout, combine options)\n\nSee `modules/manifest-parsing.md` for manifest schema details.\n\n### Step 1.3: Handle Options\n\n| Option | Behavior |\n|--------|----------|\n| `--list` | Display discovered tutorials and exit |\n| `--all` | Process all discovered manifests |\n| `--scaffold` | Create directory structure and empty files without recording |\n| `<names>` | Process only specified tutorials |\n\nWhen `--list` is specified:\n```\n**Verification:** Run the command with `--help` flag to verify availability.\nAvailable tutorials:\n  quickstart     assets/tapes/quickstart.tape\n  sync           assets/tapes/sync.tape (manifest)\n  mcp            assets/tapes/mcp.manifest.yaml (terminal + browser)\n  skill-debug    assets/tapes/skill-debug.tape\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 1.5: Validation (`tutorial-updates:validation`)\n\n**CRITICAL**: Validate tape commands BEFORE running VHS to avoid expensive regeneration cycles.\n\nSee `modules/tape-validation.md` for detailed validation logic.\n\n### Step 1.5.1: VHS Syntax Validation\n\nCheck each tape file for valid VHS syntax:\n\n```bash\n# Required: Output directive exists\ngrep -q '^Output ' \"$tape_file\" || echo \"ERROR: Missing Output directive\"\n\n# Check for balanced quotes in Type directives\ngrep '^Type ' \"$tape_file\" | while read -r line; do\n  quote_count=$(echo \"$line\" | tr -cd '\"' | wc -c)\n  if [ $((quote_count % 2)) -ne 0 ]; then\n    echo \"ERROR: Unbalanced quotes: $line\"\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.2: Extract and Validate CLI Commands\n\nFor each `Type` directive, extract the command and validate flags:\n\n```bash\n# Extract commands from Type directives\ngrep '^Type ' \"$tape_file\" | sed 's/^Type \"//' | sed 's/\"$//' | while read -r cmd; do\n  # Skip comments, clear, and echo commands\n  [[ \"$cmd\" =~ ^# ]] && continue\n  [[ \"$cmd\" == \"clear\" ]] && continue\n\n  # For skrills commands, validate flags exist\n  if [[ \"$cmd\" =~ ^skrills ]]; then\n    base_cmd=$(echo \"$cmd\" | awk '{print $1, $2}')\n    flags=$(echo \"$cmd\" | grep -oE '\\-\\-[a-zA-Z0-9-]+' || true)\n\n    for flag in $flags; do\n      if ! $base_cmd --help 2>&1 | grep -q -- \"$flag\"; then\n        echo \"ERROR: Invalid flag '$flag' in command: $cmd\"\n      fi\n    done\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.3: Verify Demo Data Exists\n\nIf the tape uses demo data, verify it exists and is populated:\n\n```bash\n# Check SKRILLS_SKILL_DIR if set\nskill_dir=$(grep '^Env SKRILLS_SKILL_DIR' \"$tape_file\" | sed 's/.*\"\\(.*\\)\"/\\1/')\nif [ -n \"$skill_dir\" ]; then\n  if [ ! -d \"$skill_dir\" ]; then\n    echo \"ERROR: Demo skill directory missing: $skill_dir\"\n  else\n    skill_count=$(find \"$skill_dir\" -name \"SKILL.md\" 2>/dev/null | wc -l)\n    if [ \"$skill_count\" -eq 0 ]; then\n      echo \"ERROR: No skills in demo directory: $skill_dir\"\n    else\n      echo \"OK: Found $skill_count demo skills in $skill_dir\"\n    fi\n  fi\nfi\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.4: Test Commands Locally\n\n**CRITICAL**: Run each extracted command locally to verify it produces expected output:\n\n```bash\n# For each command in the tape, do a quick sanity check\n# This catches issues like:\n# - Commands that exit with non-zero status\n# - Commands that produce no output (won't show anything in GIF)\n# - Commands that require user input (will hang VHS)\n\nfor cmd in $(extract_commands \"$tape_file\"); do\n  # Run with timeout to catch hanging commands\n  if ! timeout 5s bash -c \"$cmd\" &>/dev/null; then\n    echo \"WARNING: Command may fail or hang: $cmd\"\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Validation Flags\n\n| Flag | Behavior |\n|------|----------|\n| `--validate-only` | Run validation without generating GIF |\n| `--skip-validation` | Bypass validation for rapid regeneration |\n\n### Validation Exit Criteria\n\n- [ ] VHS tape syntax is valid (Output directive, balanced quotes)\n- [ ] All CLI flags in commands are valid (verified against --help)\n- [ ] Demo data directories exist and are populated\n- [ ] Commands execute successfully with expected output\n\n**If validation fails**: Stop immediately, report errors, and do NOT proceed to VHS recording.\n\n## Phase 1.6: Binary Rebuild (`tutorial-updates:rebuild`)\n\n**CRITICAL**: Ensure the binary being tested in tapes matches the latest source code. Stale binaries produce misleading demos.\n\n### Step 1.6.1: Detect Build System\n\nIdentify the project's build system:\n\n```bash\n# Check for Cargo (Rust)\nif [ -f \"Cargo.toml\" ]; then\n  BUILD_SYSTEM=\"cargo\"\n  BINARY_NAME=$(grep '^name = ' Cargo.toml | head -1 | sed 's/.*\"\\(.*\\)\"/\\1/')\n  echo \"Detected Cargo project: $BINARY_NAME\"\n# Check for Makefile\nelif [ -f \"Makefile\" ]; then\n  BUILD_SYSTEM=\"make\"\n  echo \"Detected Make project\"\n# Unknown\nelse\n  echo \"WARNING: Unknown build system, skipping binary check\"\n  BUILD_SYSTEM=\"unknown\"\nfi\n```\n**Verification:** Run `make --dry-run` to verify build configuration.\n\n### Step 1.6.2: Check Binary Freshness\n\nCompare binary modification time against Git HEAD:\n\n```bash\ncheck_binary_freshness() {\n  local binary_name=\"$1\"\n\n  # Locate binary (check cargo install location first, then PATH)\n  local binary_path=$(which \"$binary_name\" 2>/dev/null)\n\n  if [ -z \"$binary_path\" ]; then\n    echo \"WARNING: Binary '$binary_name' not found in PATH\"\n    return 1\n  fi\n\n  # Get binary modification time (Linux/macOS compatible)\n  local binary_mtime\n  if command -v stat >/dev/null 2>&1; then\n    # Linux\n    binary_mtime=$(stat -c %Y \"$binary_path\" 2>/dev/null || \\\n    # macOS\n    stat -f %m \"$binary_path\" 2>/dev/null)\n  else\n    echo \"WARNING: stat command not available, skipping freshness check\"\n    return 2\n  fi\n\n  # Get Git HEAD commit time\n  local git_head_time=$(git log -1 --format=%ct 2>/dev/null)\n\n  if [ -z \"$git_head_time\" ]; then\n    echo \"WARNING: Not a git repository, skipping freshness check\"\n    return 2\n  fi\n\n  # Compare timestamps\n  if [ \"$binary_mtime\" -lt \"$git_head_time\" ]; then\n    echo \"STALE: Binary is older than Git HEAD\"\n    echo \"  Binary: $(date -d @$binary_mtime 2>/dev/null || date -r $binary_mtime)\"\n    echo \"  HEAD:   $(date -d @$git_head_time 2>/dev/null || date -r $git_head_time)\"\n    return 1\n  else\n    echo \"OK: Binary is up-to-date\"\n    return 0\n  fi\n}\n```\n**Verification:** Run `git status` to confirm working tree state.\n\n### Step 1.6.3: Rebuild Binary\n\nRebuild using the detected build system:\n\n```bash\nrebuild_binary() {\n  local build_system=\"$1\"\n  local binary_name=\"$2\"\n\n  case \"$build_system\" in\n    cargo)\n      echo \"Rebuilding with Cargo...\"\n      # Use cargo install for CLI binaries\n      if [ -d \"crates/cli\" ]; then\n        cargo install --path crates/cli --locked --quiet\n      else\n        cargo install --path . --locked --quiet\n      fi\n      ;;\n    make)\n      echo \"Rebuilding with Make...\"\n      make build --quiet\n      ;;\n    *)\n      echo \"ERROR: Cannot rebuild, unknown build system\"\n      return 1\n      ;;\n  esac\n\n  echo \"Build complete: $binary_name\"\n}\n```\n**Verification:** Run `make --dry-run` to verify build configuration.\n\n### Step 1.6.4: Verify Binary Accessibility\n\nEnsure the rebuilt binary is accessible:\n\n```bash\nverify_binary() {\n  local binary_name=\"$1\"\n\n  if ! command -v \"$binary_name\" >/dev/null 2>&1; then\n    echo \"ERROR: Binary '$binary_name' not found after rebuild\"\n    echo \"  Check PATH includes: $HOME/.cargo/bin\"\n    return 1\n  fi\n\n  # Test binary can execute\n  if ! \"$binary_name\" --version >/dev/null 2>&1; then\n    echo \"WARNING: Binary exists but --version failed\"\n  else\n    echo \"OK: Binary is accessible and functional\"\n    \"$binary_name\" --version\n  fi\n}\n```\n**Verification:** Run `pytest -v` to verify tests pass.\n\n### Rebuild Flags\n\n| Flag | Behavior |\n|------|----------|\n| `--skip-rebuild` | Skip binary freshness check and rebuild |\n| `--force-rebuild` | Force rebuild even if binary is fresh |\n\n### Rebuild Exit Criteria\n\n- [ ] Build system detected (Cargo, Make, or explicitly skipped)\n- [ ] Binary freshness checked against Git HEAD\n- [ ] Binary rebuilt if stale (or forced)\n- [ ] Rebuilt binary is accessible in PATH\n- [ ] Binary executes successfully (--version test)\n\n**If rebuild fails**: Stop immediately, report build errors, and do NOT proceed to tape validation or VHS recording.\n\n## Phase 2: Recording (`tutorial-updates:recording`)\n\n### Step 2.1: Process Tape Components\n\nFor each tape file component:\n\n1. Parse tape file for metadata annotations (@step, @docs-brief, @book-detail)\n2. Validate Output directive exists\n3. Invoke `Skill(scry:vhs-recording)` with tape file path\n4. Verify GIF output was created\n\n### Step 2.2: Process Browser Components\n\nFor each playwright spec component:\n\n1. Check `requires` field for prerequisite commands (e.g., start server)\n2. Launch any required background processes\n3. Invoke `Skill(scry:browser-recording)` with spec path\n4. Stop background processes\n5. Invoke `Skill(scry:gif-generation)` to convert WebM to GIF\n\n### Step 2.3: Handle Multi-Component Tutorials\n\nFor manifests with `combine` section:\n\n1. Verify all component GIFs exist\n2. Invoke `Skill(scry:media-composition)` with manifest\n3. Verify combined output was created\n\n## Phase 3: Generation (`tutorial-updates:generation`)\n\n### Step 3.1: Parse Tape Annotations\n\nExtract documentation content from tape files:\n\n```tape\n# @step Install skrills\n# @docs-brief Install via cargo\n# @book-detail The recommended installation method uses cargo...\nType \"cargo install skrills\"\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\nAnnotations:\n- `@step` - Step title/heading\n- `@docs-brief` - Concise text for project docs (docs/ directory)\n- `@book-detail` - Extended text for technical book (book/ directory)\n\n### Step 3.2: Generate Dual-Tone Markdown\n\nGenerate two versions of each tutorial:\n\n1. **Project docs** (`docs/tutorials/<name>.md`)\n   - Brief, action-oriented\n   - Uses @docs-brief content\n   - Focuses on commands and quick results\n\n2. **Technical book** (`book/src/tutorials/<name>.md`)\n   - Detailed, educational\n   - Uses @book-detail content\n   - Explains concepts and rationale\n\nSee `modules/markdown-generation.md` for formatting details.\n\n### Step 3.3: Generate README Demo Section\n\nCreate or update demo section in README.md:\n\n```markdown\n## Demos\n\n### Quickstart\n![Quickstart demo](assets/gifs/quickstart.gif)\n*Install, validate, analyze, and serve in under a minute. [Full tutorial](docs/tutorials/quickstart.md)*\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 4: Integration (`tutorial-updates:integration`)\n\n### Step 4.1: Verify All Outputs\n\nConfirm all expected files exist:\n\n```bash\n# Check GIF files\nfor gif in assets/gifs/*.gif; do\n  if [[ -f \"$gif\" ]]; then\n    echo \"OK: $gif ($(du -h \"$gif\" | cut -f1))\"\n  else\n    echo \"MISSING: $gif\"\n  fi\ndone\n\n# Check markdown files\nls -la docs/tutorials/*.md 2>/dev/null\nls -la book/src/tutorials/*.md 2>/dev/null\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 4.2: Update SUMMARY.md (Book)\n\nIf the project has an mdBook structure, update `book/src/SUMMARY.md`:\n\n```markdown\n- [Tutorials](./tutorials/README.md)\n  - [Quickstart](./tutorials/quickstart.md)\n  - [Sync Workflow](./tutorials/sync.md)\n  - [MCP Integration](./tutorials/mcp.md)\n  - [Skill Debugging](./tutorials/skill-debug.md)\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 4.3: Report Results\n\nSummarize the update:\n\n```\n**Verification:** Run the command with `--help` flag to verify availability.\nTutorial Update Complete\n========================\nTutorials processed: 4\nGIFs generated: 5\n  - quickstart.gif (1.2MB)\n  - sync.gif (980KB)\n  - mcp-terminal.gif (1.5MB)\n  - mcp-browser.gif (2.1MB)\n  - skill-debug.gif (890KB)\n\nMarkdown generated:\n  - docs/tutorials/ (4 files)\n  - book/src/tutorials/ (4 files)\n\nREADME demo section updated\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Exit Criteria\n\n- [ ] All specified tutorials processed (or all if --all)\n- [ ] GIF files created at manifest-specified paths\n- [ ] Dual-tone markdown generated for each tutorial\n- [ ] README demo section updated with GIF embeds\n- [ ] Book SUMMARY.md updated (if applicable)\n- [ ] All TodoWrite items completed\n\n## Error Handling\n\n| Error | Resolution |\n|-------|------------|\n| VHS not installed | `go install github.com/charmbracelet/vhs@latest` |\n| Playwright not installed | `npm install -D @playwright/test && npx playwright install chromium` |\n| Tape file missing Output | Add `Output assets/gifs/<name>.gif` directive |\n| Browser spec requires server | Start server before running spec |\n| GIF too large | Adjust fps/scale in gif-generation |\n\n## Scaffold Mode\n\nWhen `--scaffold` is specified, create structure without recording:\n\n1. Create `assets/tapes/` directory\n2. Create `assets/gifs/` directory\n3. Create `assets/browser/` directory (if browser tutorials planned)\n4. Create template tape file with metadata annotations\n5. Create template manifest file\n6. Create empty markdown files in docs/tutorials/ and book/src/tutorials/\n\nTemplate tape file:\n```tape\n# @title: Tutorial Name\n# @description: Brief description of the tutorial\n\nOutput assets/gifs/tutorial-name.gif\nSet FontSize 14\nSet Width 1200\nSet Height 600\nSet Theme \"Catppuccin Mocha\"\n\n# @step Step 1 Title\n# @docs-brief Brief docs text\n# @book-detail Extended book text with more context and explanation\nType \"command here\"\nEnter\nSleep 2s\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\nFile v1.9.19:_meta.json\n\n{\n  \"ownerId\": \"kn7d107jg9jv602h9ytsegydq184a42s\",\n  \"slug\": \"nm-sanctum-tutorial-updates\",\n  \"version\": \"1.9.19\",\n  \"publishedAt\": 1787750477158\n}\n\nFile v1.9.19:modules/manifest-parsing.md\n\n# Manifest Parsing Module\n\nParse `.manifest.yaml` files and tape file annotations for tutorial orchestration.\n\n## Manifest Schema\n\nTutorial manifests define multi-component tutorials with composition rules:\n\n```yaml\n# Full manifest schema\nname: string              # Required: identifier for the tutorial\ntitle: string             # Optional: human-readable title\ndescription: string       # Optional: brief description\n\ncomponents:               # Required: list of media components\n  - type: tape            # Component type: tape, playwright, static\n    source: path/to.tape  # Path to source file (relative to manifest)\n    output: path/to.gif   # Path for generated output\n    options:              # Optional: component-specific options\n      fps: 10\n      width: 800\n\n  - type: playwright\n    source: browser/spec.ts\n    output: assets/gifs/browser.gif\n    requires:             # Optional: commands to run before\n      - \"npm run serve\"\n\n  - type: static          # Pre-existing asset (no generation)\n    source: existing.gif\n    output: existing.gif\n\ncombine:                  # Optional: composition rules\n  output: combined.gif    # Path for combined output\n  layout: vertical        # Layout: vertical, horizontal, sequential, grid, pip\n  options:\n    padding: 10\n    background: \"#1a1a2e\"\n```\n\n## Component Types\n\n### Tape Components\n\nVHS tape files for terminal recordings:\n\n```yaml\n- type: tape\n  source: quickstart.tape\n  output: assets/gifs/quickstart.gif\n  options:\n    # Override tape file settings if needed\n    width: 1000\n    height: 500\n```\n\n### Playwright Components\n\nBrowser automation specs:\n\n```yaml\n- type: playwright\n  source: browser/mcp-dashboard.spec.ts\n  output: assets/gifs/mcp-browser.gif\n  requires:\n    - \"skrills serve\"\n  options:\n    fps: 12\n    width: 1280\n```\n\nThe `requires` array specifies commands to run before the spec. These run as background processes and are terminated after recording.\n\n### Static Components\n\nPre-existing assets that don't need generation:\n\n```yaml\n- type: static\n  source: diagrams/architecture.gif\n  output: diagrams/architecture.gif\n```\n\n## Parsing Tape File Annotations\n\nTape files contain inline annotations for documentation generation:\n\n### Annotation Format\n\n```tape\n# @title: Tutorial Title\n# @description: Brief description for README\n\n# @step Step Name\n# @docs-brief Concise text for project docs\n# @book-detail Extended explanation for technical book\nType \"command\"\nEnter\n```\n\n### Annotation Types\n\n| Annotation | Scope | Purpose |\n|------------|-------|---------|\n| `@title` | File | Tutorial title |\n| `@description` | File | Brief description |\n| `@step` | Block | Step heading |\n| `@docs-brief` | Block | Concise docs text |\n| `@book-detail` | Block | Extended book text |\n\n### Parsing Algorithm\n\n```python\ndef parse_tape_annotations(tape_content: str) -> dict:\n    \"\"\"Parse tape file for documentation annotations.\"\"\"\n    result = {\n        \"title\": None,\n        \"description\": None,\n        \"steps\": []\n    }\n\n    current_step = None\n\n    for line in tape_content.splitlines():\n        line = line.strip()\n\n        # File-level annotations\n        if line.startswith(\"# @title:\"):\n            result[\"title\"] = line.split(\":\", 1)[1].strip()\n        elif line.startswith(\"# @description:\"):\n            result[\"description\"] = line.split(\":\", 1)[1].strip()\n\n        # Step-level annotations\n        elif line.startswith(\"# @step\"):\n            # Save previous step\n            if current_step:\n                result[\"steps\"].append(current_step)\n            # Start new step\n            step_name = line.replace(\"# @step\", \"\").strip()\n            current_step = {\n                \"name\": step_name,\n                \"docs_brief\": None,\n                \"book_detail\": None,\n                \"commands\": []\n            }\n        elif line.startswith(\"# @docs-brief\"):\n            if current_step:\n                current_step[\"docs_brief\"] = line.replace(\"# @docs-brief\", \"\").strip()\n        elif line.startswith(\"# @book-detail\"):\n            if current_step:\n                current_step[\"book_detail\"] = line.replace(\"# @book-detail\", \"\").strip()\n\n        # Command lines (Type, Enter, etc.)\n        elif current_step and line.startswith(\"Type\"):\n            # Extract command text\n            match = re.match(r'Type(?:@\\d+ms)?\\s+\"(.+)\"', line)\n            if match:\n                current_step[\"commands\"].append(match.group(1))\n\n    # Don't forget last step\n    if current_step:\n        result[\"steps\"].append(current_step)\n\n    return result\n```\n\n## Manifest Validation\n\n### Required Fields\n\n```bash\n# Validate manifest has required fields\nyq eval '.name' manifest.yaml >/dev/null || echo \"ERROR: missing name\"\nyq eval '.components | length > 0' manifest.yaml | grep -q true || echo \"ERROR: no components\"\n\n# Validate each component\nfor i in $(seq 0 $(($(yq eval '.components | length' manifest.yaml) - 1))); do\n  yq eval \".components[$i].type\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing type\"\n  yq eval \".components[$i].source\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing source\"\n  yq eval \".components[$i].output\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing output\"\ndone\n```\n\n### Source File Validation\n\n```bash\n# Check all source files exist\nfor source in $(yq eval '.components[].source' manifest.yaml); do\n  if [[ ! -f \"$source\" ]]; then\n    echo \"ERROR: Source file not found: $source\"\n  fi\ndone\n```\n\n## Discovery Patterns\n\n### Find All Manifests\n\n```bash\n# Find manifest files in common locations\nfind . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null\nfind assets -name \"*.manifest.yaml\" -type f 2>/dev/null\nfind tutorials -name \"*.manifest.yaml\" -type f 2>/dev/null\n```\n\n### Find Standalone Tape Files\n\nTape files without manifests (single-component tutorials):\n\n```bash\n# Find tape files\nfind . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null\n\n# Filter out those with manifests\nfor tape in $(find . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  manifest=\"${tape%.tape}.manifest.yaml\"\n  if [[ ! -f \"$manifest\" ]]; then\n    echo \"Standalone: $tape\"\n  fi\ndone\n```\n\n### Build Tutorial Index\n\n```bash\n# Create index of all tutorials\necho \"Tutorials:\"\necho \"==========\"\n\n# From manifests\nfor manifest in $(find . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  name=$(yq eval '.name' \"$manifest\")\n  title=$(yq eval '.title // .name' \"$manifest\")\n  components=$(yq eval '.components | length' \"$manifest\")\n  echo \"  $name: $title ($components components) [manifest]\"\ndone\n\n# Standalone tapes\nfor tape in $(find . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  manifest=\"${tape%.tape}.manifest.yaml\"\n  if [[ ! -f \"$manifest\" ]]; then\n    name=$(basename \"$tape\" .tape)\n    echo \"  $name: $tape [standalone]\"\n  fi\ndone\n```\n\n## Error Handling\n\n| Error | Resolution |\n|-------|------------|\n| Manifest parse error | Validate YAML syntax with `yq eval '.' manifest.yaml` |\n| Missing source file | Check path is relative to manifest location |\n| Unknown component type | Use `tape`, `playwright`, or `static` |\n| Missing combine output | Add `combine.output` field if combine section exists |\n| Circular requires | validate background processes don't depend on each other |\n\n## Example Manifests\n\n### Simple Tape Tutorial\n\n```yaml\nname: quickstart\ntitle: \"Quickstart Guide\"\ncomponents:\n  - type: tape\n    source: quickstart.tape\n    output: assets/gifs/quickstart.gif\n```\n\n### Multi-Component Tutorial\n\n```yaml\nname: mcp\ntitle: \"MCP Server Integration\"\ndescription: \"Terminal and browser demo of MCP server\"\ncomponents:\n  - type: tape\n    source: mcp-terminal.tape\n    output: assets/gifs/mcp-terminal.gif\n  - type: playwright\n    source: browser/mcp-dashboard.spec.ts\n    output: assets/gifs/mcp-browser.gif\n    requires:\n      - \"skrills serve\"\ncombine:\n  output: assets/gifs/mcp-combined.gif\n  layout: vertical\n  options:\n    padding: 10\n    background: \"#0d1117\"\n```\n\n### Tutorial with Static Assets\n\n```yaml\nname: architecture\ntitle: \"Architecture Overview\"\ncomponents:\n  - type: tape\n    source: arch-demo.tape\n    output: assets/gifs/arch-demo.gif\n  - type: static\n    source: diagrams/system-overview.gif\n    output: diagrams/system-overview.gif\ncombine:\n  output: assets/gifs/architecture-full.gif\n  layout: sequential\n```\n\nFile v1.9.19:modules/markdown-generation.md\n\n# Markdown Generation Module\n\nGenerate dual-tone markdown documentation from tape file annotations and manifest metadata.\n\n## Dual-Tone System\n\nTutorials are generated in two tones for different audiences:\n\n| Tone | Location | Audience | Style |\n|------|----------|----------|-------|\n| **Project Docs** | `docs/tutorials/` | Users getting started | Concise, action-oriented |\n| **Technical Book** | `book/src/tutorials/` | Developers learning deeply | Detailed, educational |\n\n## Annotation Sources\n\nContent comes from tape file annotations:\n\n```tape\n# @step Install the CLI\n# @docs-brief Install via cargo with a single command\n# @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.\nType \"cargo install skrills\"\n```\n\n- `@docs-brief` - Used for project docs (brief, focused)\n- `@book-detail` - Used for technical book (extended, contextual)\n- If only one is present, use it for both\n- If neither is present, generate minimal text from step name\n\n## Project Docs Format\n\n### Template Structure\n\n```markdown\n# {Tutorial Title}\n\n{Description from @description}\n\n![Demo]({relative-path-to-gif})\n\n## Prerequisites\n\n- Prerequisite 1\n- Prerequisite 2\n\n## Steps\n\n### {Step 1 Name}\n\n{@docs-brief content}\n\n```bash\n{command from Type directive}\n```\n\n### {Step 2 Name}\n\n{@docs-brief content}\n\n```bash\n{command}\n```\n\n## Next Steps\n\n- Link to related tutorial 1\n- Link to related tutorial 2\n```\n\n### Example Output\n\n```markdown\n# Quickstart\n\nInstall, validate, analyze, and serve in under a minute.\n\n![Quickstart demo](../../assets/gifs/quickstart.gif)\n\n## Prerequisites\n\n- Rust toolchain installed (`rustup`)\n- Terminal with UTF-8 support\n\n## Steps\n\n### Install skrills\n\nInstall via cargo with a single command.\n\n```bash\ncargo install skrills\n```\n\n### Validate Skills\n\nValidate and auto-fix missing frontmatter.\n\n```bash\nskrills validate --target codex --autofix\n```\n\n### Analyze Token Usage\n\nAnalyze skills for token optimization opportunities.\n\n```bash\nskrills analyze --min-tokens 500 --suggestions\n```\n\n### Start MCP Server\n\nStart the MCP server.\n\n```bash\nskrills serve\n```\n\n## Next Steps\n\n- [Sync Workflow](./sync.md) - Bidirectional sync between Claude Code and Codex CLI\n- [MCP Integration](./mcp.md) - Use skrills as an MCP server\n```\n\n## Technical Book Format\n\n### Template Structure\n\n```markdown\n# {Tutorial Title}\n\n{Extended description}\n\n## Overview\n\n{Context and learning objectives}\n\n![Demo]({relative-path-to-gif})\n\n## {Step 1 Name}\n\n{@book-detail content - multiple paragraphs allowed}\n\n```bash\n{command from Type directive}\n```\n\n{Additional explanation of what the command does}\n\n## {Step 2 Name}\n\n{@book-detail content}\n\n```bash\n{command}\n```\n\n{Explanation of output and next steps}\n\n## Summary\n\n{Key takeaways}\n\n## Further Reading\n\n- Internal link 1\n- External reference 1\n```\n\n### Example Output\n\n```markdown\n# Quickstart\n\nThis guide walks through the complete skrills workflow: installation, validation, analysis, and serving skills via MCP.\n\n## Overview\n\nWhat this guide covers:\n\n- How to install skrills using cargo\n- The difference between Claude Code and Codex CLI validation targets\n- How to analyze skills for token optimization\n- How to expose skills via the MCP protocol\n\n![Quickstart demo](../assets/gifs/quickstart.gif)\n\n## Install skrills\n\nThe 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.\n\n```bash\ncargo install skrills\n```\n\nThe binary will be placed in `~/.cargo/bin/`, which should be in your PATH if you installed Rust using rustup.\n\n## Validate Skills\n\nSkrills 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.\n\n```bash\nskrills validate --target codex --autofix\n```\n\nThe `--autofix` flag automatically derives missing frontmatter from the file path and content:\n1. Parses the skill filename to derive `name`\n2. Extracts the first paragraph as `description`\n3. Inserts YAML frontmatter at the file start\n\nThis makes migration from Claude Code to Codex straightforward.\n\n## Analyze Token Usage\n\nThe analyzer reports token counts for each skill and suggests optimizations for large skills that may consume excessive context.\n\n```bash\nskrills analyze --min-tokens 500 --suggestions\n```\n\nSkills exceeding the threshold are flagged with specific recommendations:\n- Split into multiple focused skills\n- Extract reusable modules\n- Remove redundant content\n\n## Start MCP Server\n\nWhen running as an MCP server, skrills exposes tools for skill discovery, validation, and analysis to any MCP-compatible client.\n\n```bash\nskrills serve\n```\n\nThe server listens on the default MCP port and responds to tool invocations from Claude Code or other clients.\n\n## Summary\n\n- Install skrills with `cargo install skrills`\n- Validate skills for Codex compatibility with `--target codex`\n- Use `--autofix` to automatically add required frontmatter\n- Analyze token usage to optimize context consumption\n- Serve skills via MCP for integration with AI assistants\n\n## Further Reading\n\n- [Sync Workflow](./sync.md) - Bidirectional synchronization between skill repositories\n- [MCP Integration](./mcp.md) - Advanced MCP server configuration\n- [Skill Debugging](./skill-debug.md) - Troubleshooting skill loading issues\n```\n\n## GIF Embedding\n\n### Relative Path Calculation\n\nGIFs are embedded with paths relative to the markdown file:\n\n| Markdown Location | GIF Location | Relative Path |\n|-------------------|--------------|---------------|\n| `docs/tutorials/quickstart.md` | `assets/gifs/quickstart.gif` | `../../assets/gifs/quickstart.gif` |\n| `book/src/tutorials/quickstart.md` | `assets/gifs/quickstart.gif` | `../../../assets/gifs/quickstart.gif` |\n| `README.md` | `assets/gifs/quickstart.gif` | `assets/gifs/quickstart.gif` |\n\n### Path Calculation Algorithm\n\n```python\ndef relative_gif_path(markdown_path: str, gif_path: str) -> str:\n    \"\"\"Calculate relative path from markdown file to GIF.\"\"\"\n    from pathlib import Path\n\n    md = Path(markdown_path)\n    gif = Path(gif_path)\n\n    # Get common ancestor\n    common = Path(*os.path.commonprefix([md.parts, gif.parts]))\n\n    # Calculate relative path\n    md_depth = len(md.parent.relative_to(common).parts)\n    gif_relative = gif.relative_to(common)\n\n    return \"../\" * md_depth + str(gif_relative)\n```\n\n### Embedding Format\n\n```markdown\n![{Alt text}]({relative-path})\n```\n\nFor multi-component tutorials with combined GIF:\n\n```markdown\n![{Tutorial title} demo]({relative-path-to-combined-gif})\n\n*This demo shows both terminal and browser interactions.*\n```\n\n## README Integration\n\n### Demo Section Template\n\n```markdown\n## Demos\n\n### {Tutorial 1 Title}\n![{Tutorial 1 title} demo]({gif-path})\n*{Description}. [Full tutorial]({docs-tutorial-path})*\n\n### {Tutorial 2 Title}\n![{Tutorial 2 title} demo]({gif-path})\n*{Description}. [Full tutorial]({docs-tutorial-path})*\n```\n\n### Generating README Section\n\n```python\ndef generate_readme_demos(tutorials: list) -> str:\n    \"\"\"Generate demo section for README.\"\"\"\n    lines = [\"## Demos\", \"\"]\n\n    for tutorial in tutorials:\n        lines.extend([\n            f\"### {tutorial['title']}\",\n            f\"![{tutorial['title']} demo]({tutorial['gif_path']})\",\n            f\"*{tutorial['description']}. [Full tutorial]({tutorial['docs_path']})*\",\n            \"\"\n        ])\n\n    return \"\\n\".join(lines)\n```\n\n### Updating README\n\nReplace the existing demo section or append if not present:\n\n```bash\n# Check if demo section exists\nif grep -q \"^## Demos\" README.md; then\n  # Replace section (between ## Demos and next ##)\n  sed -i '/^## Demos/,/^## [^D]/{ /^## [^D]/!d }' README.md\n  # Insert new content after ## Demos\nfi\n```\n\n## Book SUMMARY.md Integration\n\n### Template\n\n```markdown\n- [Tutorials](./tutorials/README.md)\n  - [{Tutorial 1 Title}](./tutorials/{name1}.md)\n  - [{Tutorial 2 Title}](./tutorials/{name2}.md)\n```\n\n### Detection and Update\n\n```bash\n# Check if tutorials section exists in SUMMARY.md\nif [[ -f \"book/src/SUMMARY.md\" ]]; then\n  if grep -q \"Tutorials\" book/src/SUMMARY.md; then\n    echo \"Tutorials section exists - update entries\"\n  else\n    echo \"Add Tutorials section to SUMMARY.md\"\n  fi\nfi\n```\n\n## Content Guidelines\n\n### Project Docs Style\n\n- Action-oriented imperatives: \"Install\", \"Run\", \"Configure\"\n- One paragraph per step maximum\n- Focus on commands and results\n- Minimal explanation of \"why\"\n- Include prerequisites section\n\n### Technical Book Style\n\n- Educational tone: \"This guide explains...\"\n- Multiple paragraphs allowed per step\n- Explain rationale and context\n- Include troubleshooting tips\n- Reference related concepts\n- Add summary and further reading sections\n\n### Common Rules (Both)\n\n- No filler phrases (\"simply\", \"just\", \"easily\")\n- No emojis or decorative elements\n- Grounded, specific language\n- Code blocks for all commands\n- Consistent heading hierarchy\n- Prose text wraps at 80 chars per line (hybrid wrapping:\n  prefer sentence/clause boundaries over arbitrary breaks)\n- Blank line before and after every heading\n- ATX headings only (`#` prefix, no setext underlines)\n- Blank line before every list\n- Reference-style links when inline links push past 80 chars\n- Full formatting spec: `Skill(leyline:markdown-formatting)`\n\n## Error Handling\n\n| Issue | Resolution |\n|-------|------------|\n| Missing @docs-brief | Fall back to @book-detail or step name |\n| Missing @book-detail | Fall back to @docs-brief or generate minimal text |\n| No Output directive | Skip GIF embed, log warning |\n| Invalid path calculation | Verify markdown and GIF paths are relative to project root |\n| README section conflict | Preserve user content outside ## Demos section |\n\nFile v1.9.19:modules/tape-validation.md\n\n# Tape Validation Module\n\nPre-flight validation for VHS tape files before GIF generation. Validate commands work correctly and demo data exists BEFORE running the time-consuming VHS recording.\n\n## Overview\n\nThis module catches errors early:\n- Stale binaries detected before recording (Phase 0)\n- Invalid CLI flags discovered before GIF generation\n- Missing demo data detected before recording starts\n- VHS syntax issues reported before execution\n\n## Validation Phases\n\n### Phase 0: Binary Freshness Check\n\n**CRITICAL**: Verify the CLI binary matches the latest source code. Stale binaries produce misleading demos.\n\n```bash\n# Check if binary is older than Git HEAD\ncheck_binary_freshness() {\n  local binary_name=\"$1\"\n\n  # Locate binary in PATH\n  local binary_path=$(which \"$binary_name\" 2>/dev/null)\n\n  if [ -z \"$binary_path\" ]; then\n    echo \"WARNING: Binary '$binary_name' not found in PATH\"\n    return 1\n  fi\n\n  # Get binary modification time (Linux/macOS compatible)\n  local binary_mtime\n  binary_mtime=$(stat -c %Y \"$binary_path\" 2>/dev/null || \\\n                 stat -f %m \"$binary_path\" 2>/dev/null)\n\n  # Get Git HEAD commit time\n  local git_head_time=$(git log -1 --format=%ct 2>/dev/null)\n\n  if [ -z \"$git_head_time\" ]; then\n    echo \"WARNING: Not a git repository, skipping freshness check\"\n    return 2\n  fi\n\n  # Compare timestamps\n  if [ \"$binary_mtime\" -lt \"$git_head_time\" ]; then\n    echo \"STALE: Binary is older than Git HEAD\"\n    echo \"  Binary: $(date -d @$binary_mtime 2>/dev/null || date -r $binary_mtime)\"\n    echo \"  HEAD:   $(date -d @$git_head_time 2>/dev/null || date -r $git_head_time)\"\n    echo \"\"\n    echo \"RECOMMENDATION: Rebuild binary before recording tape\"\n    echo \"  cargo install --path crates/cli --locked\"\n    return 1\n  else\n    echo \"OK: Binary is up-to-date with Git HEAD\"\n    return 0\n  fi\n}\n```\n\n**Error handling**:\n- Exit code 0: Binary is fresh\n- Exit code 1: Binary is stale (older than HEAD)\n- Exit code 2: Cannot determine (not in git repo or binary not found)\n\n**Integration**:\n```bash\n# Run before tape validation\ncheck_binary_freshness \"skrills\"\nif [ $? -eq 1 ]; then\n  echo \"ERROR: Stale binary detected\"\n  echo \"Run 'cargo install --path crates/cli --locked' to rebuild\"\n  exit 1\nfi\n```\n\n## Command Validation Phases\n\n### 1. VHS Syntax Validation\n\nCheck that the tape file has valid VHS syntax:\n\n```bash\n# Required: Output directive\ngrep -q '^Output ' \"$tape_file\" || echo \"ERROR: Missing Output directive\"\n\n# Check for balanced quotes in Type directives\ngrep '^Type ' \"$tape_file\" | while read -r line; do\n  # Count quotes (should be even)\n  quote_count=$(echo \"$line\" | tr -cd '\"' | wc -c)\n  if [ $((quote_count % 2)) -ne 0 ]; then\n    echo \"ERROR: Unbalanced quotes in: $line\"\n  fi\ndone\n```\n\n**Error examples**:\n- \"Missing Output directive in quickstart.tape:1\"\n- \"Unbalanced quotes in Type directive at quickstart.tape:15\"\n\n### 2. Command Extraction\n\nParse `Type` directives to extract shell commands:\n\n```bash\n# Extract commands from Type directives\nextract_commands() {\n  local tape_file=\"$1\"\n  grep '^Type ' \"$tape_file\" | \\\n    sed 's/^Type \"//' | \\\n    sed 's/\"$//' | \\\n    grep -v '^#' | \\\n    grep -v '^clear$' | \\\n    grep -v '^$'\n}\n```\n\n**Example**:\n```vhs\nType \"skrills validate --errors-only\"\n```\nExtracts: `skrills validate --errors-only`\n\n### 3. CLI Flag Validation\n\nFor each extracted command, validate flags exist:\n\n```bash\nvalidate_command_flags() {\n  local cmd=\"$1\"\n  local line_num=\"$2\"\n\n  # Extract the base command (e.g., \"skrills validate\")\n  local base_cmd=$(echo \"$cmd\" | awk '{print $1, $2}')\n\n  # Get help output for flag discovery\n  local help_output=$($base_cmd --help 2>&1)\n\n  # Extract flags from the command\n  local flags=$(echo \"$cmd\" | grep -oE '\\-\\-[a-zA-Z0-9-]+')\n\n  for flag in $flags; do\n    if ! echo \"$help_output\" | grep -q -- \"$flag\"; then\n      echo \"ERROR: Invalid flag '$flag' at line $line_num\"\n      echo \"  Command: $cmd\"\n      echo \"  Run '$base_cmd --help' to see available flags\"\n    fi\n  done\n}\n```\n\n**Example validation**:\n```bash\n# Command from tape line 12\nskrills create demo --sample basic\n\n# Validation\n$ skrills create --help | grep -- '--sample'\n# (no output - flag doesn't exist)\n\n# Error output\nERROR: Invalid flag '--sample' at line 12\n  Command: skrills create demo --sample basic\n  Run 'skrills create --help' to see available flags\n```\n\n### 4. Demo Data Verification\n\nCheck that demo directories and skills exist:\n\n```bash\nverify_demo_data() {\n  local tape_file=\"$1\"\n\n  # Extract Env SKRILLS_SKILL_DIR if set\n  local skill_dir=$(grep '^Env SKRILLS_SKILL_DIR' \"$tape_file\" | \\\n    sed 's/.*\"\\(.*\\)\"/\\1/')\n\n  if [ -n \"$skill_dir\" ]; then\n    if [ ! -d \"$skill_dir\" ]; then\n      echo \"ERROR: Demo skill directory does not exist: $skill_dir\"\n      return 1\n    fi\n\n    # Check it has content\n    local skill_count=$(find \"$skill_dir\" -name \"SKILL.md\" 2>/dev/null | wc -l)\n    if [ \"$skill_count\" -eq 0 ]; then\n      echo \"ERROR: Demo skill directory is empty: $skill_dir\"\n      echo \"  Expected at least one SKILL.md file\"\n      return 1\n    fi\n\n    echo \"OK: Demo skills found: $skill_count skills in $skill_dir\"\n  fi\n\n  # Check for other referenced directories\n  grep '^Type.*mkdir\\|^Type.*cp\\|^Type.*cd' \"$tape_file\" | while read -r line; do\n    # Extract directory paths and verify parent exists\n    # This is optional - catches obvious path errors\n    true\n  done\n}\n```\n\n### 5. Expected Output Verification\n\nFor commands that should produce visible output, verify they will:\n\n```bash\nverify_expected_output() {\n  local cmd=\"$1\"\n\n  # Skip echo commands (always produce output)\n  echo \"$cmd\" | grep -q '^echo ' && return 0\n\n  # For skrills commands, do a dry-run to verify output\n  if echo \"$cmd\" | grep -q '^skrills '; then\n    # Run with --help to verify command exists\n    local base=$(echo \"$cmd\" | awk '{print $1, $2}')\n    if ! $base --help &>/dev/null; then\n      echo \"WARNING: Command may not produce output: $cmd\"\n    fi\n  fi\n}\n```\n\n## Complete Validation Script\n\n```bash\n#!/bin/bash\n# validate_tape.sh - Pre-flight validation for VHS tape files\n\nvalidate_tape() {\n  local tape_file=\"$1\"\n  local errors=0\n\n  echo \"=== Validating: $tape_file ===\"\n\n  # Phase 1: Syntax\n  echo \"Phase 1: VHS Syntax...\"\n  if ! grep -q '^Output ' \"$tape_file\"; then\n    echo \"  ERROR: Missing Output directive\"\n    ((errors++))\n  else\n    echo \"  OK: Output directive found\"\n  fi\n\n  # Phase 2: Extract commands\n  echo \"Phase 2: Extracting commands...\"\n  local line_num=0\n  local cmd_count=0\n  while IFS= read -r line; do\n    ((line_num++))\n    if [[ \"$line\" =~ ^Type\\ \\\" ]]; then\n      cmd=$(echo \"$line\" | sed 's/^Type \"//' | sed 's/\"$//')\n      ((cmd_count++))\n\n      # Phase 3: Validate CLI flags\n      if [[ \"$cmd\" =~ ^skrills ]]; then\n        base_cmd=$(echo \"$cmd\" | awk '{print $1, $2}')\n        flags=$(echo \"$cmd\" | grep -oE '\\-\\-[a-zA-Z0-9-]+' || true)\n\n        if [ -n \"$flags\" ]; then\n          help_output=$($base_cmd --help 2>&1 || echo \"\")\n          for flag in $flags; do\n            if ! echo \"$help_output\" | grep -q -- \"$flag\"; then\n              echo \"  ERROR: Invalid flag '$flag' at line $line_num\"\n              echo \"         Command: $cmd\"\n              ((errors++))\n            fi\n          done\n        fi\n      fi\n    fi\n  done < \"$tape_file\"\n  echo \"  Extracted $cmd_count commands\"\n\n  # Phase 4: Demo data\n  echo \"Phase 4: Demo data verification...\"\n  skill_dir=$(grep '^Env SKRILLS_SKILL_DIR' \"$tape_file\" | sed 's/.*\"\\(.*\\)\"/\\1/' || true)\n  if [ -n \"$skill_dir\" ]; then\n    if [ ! -d \"$skill_dir\" ]; then\n      echo \"  ERROR: Demo skill directory missing: $skill_dir\"\n      ((errors++))\n    else\n      skill_count=$(find \"$skill_dir\" -name \"SKILL.md\" 2>/dev/null | wc -l)\n      if [ \"$skill_count\" -eq 0 ]; then\n        echo \"  ERROR: No skills in demo directory: $skill_dir\"\n        ((errors++))\n      else\n        echo \"  OK: Found $skill_count demo skills\"\n      fi\n    fi\n  fi\n\n  # Summary\n  echo \"=== Validation Complete ===\"\n  if [ \"$errors\" -eq 0 ]; then\n    echo \"PASSED: No errors found\"\n    return 0\n  else\n    echo \"FAILED: $errors error(s) found\"\n    return 1\n  fi\n}\n\n# Run if called directly\nif [ -n \"$1\" ]; then\n  validate_tape \"$1\"\nfi\n```\n\n## Integration with Workflow\n\nBefore running VHS, execute validation:\n\n```bash\n# In the tutorial-updates skill Phase 1.5\nvalidate_tape \"$tape_file\"\nif [ $? -ne 0 ]; then\n  echo \"Validation failed. Fix errors before running VHS.\"\n  exit 1\nfi\n\n# Proceed to VHS recording\nvhs \"$tape_file\"\n```\n\n## Flags\n\n### `--validate-only`\n\nRun validation without generating GIF:\n\n```bash\n# Validate all tapes without recording\nfor tape in assets/tapes/*.tape; do\n  validate_tape \"$tape\"\ndone\n```\n\n### `--skip-validation`\n\nBypass validation for rapid regeneration:\n\n```bash\n# Skip validation when commands are known-good\nif [ \"$SKIP_VALIDATION\" != \"true\" ]; then\n  validate_tape \"$tape_file\" || exit 1\nfi\nvhs \"$tape_file\"\n```\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| 0 | Validation passed |\n| 1 | Validation failed (errors found) |\n| 2 | Validation skipped (--skip-validation) |\n\n## Error Message Format\n\nStandardized format for actionable errors:\n\n```\nERROR: <short-description> at line <number>\n  Command: <full-command>\n  <hint-or-available-options>\n```\n\nExample:\n```\nERROR: Invalid flag '--sample' at line 12\n  Command: skrills validate --sample 5\n  Run 'skrills validate --help' to see available flags\n```\n\nFile v1.9.19:skill-card.md\n\n## Description:\n\nGenerates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[athola](https://clawhub.ai/user/athola)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and documentation maintainers use this skill to discover tutorial assets, validate VHS tapes and Playwright specs, generate GIF recordings, and produce concise docs plus longer book-style tutorial markdown.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: The workflow may run repository-controlled tape commands, Playwright specs, Makefile targets, Cargo build scripts, or install commands with broad local authority.\n\nMitigation: Review those files before use and run the workflow in a sandbox or disposable checkout without secrets.\n\nRisk: Generated tutorials and recordings can become misleading if commands fail, hang, use missing demo data, or rely on stale binaries.\n\nMitigation: Run the documented pre-flight validation, check binary freshness, verify demo data, and inspect generated markdown and GIF outputs before publishing.\n\n## Reference(s):\n\n- [Sanctum plugin source](https://github.com/athola/claude-night-market/tree/master/plugins/sanctum)\n- [ClawHub skill page](https://clawhub.ai/athola/skills/nm-sanctum-tutorial-updates)\n\n## Skill Output:\n\n**Output Type(s):** [Markdown, Shell commands, Configuration, Guidance]\n\n**Output Format:** [Markdown with inline shell commands and tutorial asset references]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [Can produce docs, book tutorial pages, README demo sections, GIF references, validation reports, and scaffolding guidance.]\n\n## Skill Version(s):\n\n1.9.19 (source: server release metadata; artifact frontmatter reports 1.9.8)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v1.9.17: 6 files, 18354 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10011b), modules/tape-validation.md (9405b), skill-card.md (2605b), SKILL.md (20046b), _meta.json (147b)\n\nFile v1.9.17:SKILL.md\n\n---\nname: tutorial-updates\ndescription: |\n  Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording\nversion: 1.9.8\ntriggers:\n  - tutorial\n  - gif\n  - vhs\n  - playwright\n  - documentation\n  - demo\n  - tutorial assets need refreshing\nmetadata: {\"openclaw\": {\"homepage\": \"https://github.com/athola/claude-night-market/tree/master/plugins/sanctum\", \"emoji\": \"\\ud83e\\udd9e\", \"requires\": {\"config\": [\"night-market.sanctum:shared\", \"night-market.sanctum:git-workspace-review\", \"night-market.scry:vhs-recording\", \"night-market.scry:browser-recording\", \"night-market.scry:gif-generation\", \"night-market.scry:media-composition\"]}}}\nsource: claude-night-market\nsource_plugin: sanctum\n---\n\n> **Night Market Skill** — ported from [claude-night-market/sanctum](https://github.com/athola/claude-night-market/tree/master/plugins/sanctum). For the full experience with agents, hooks, and commands, install the Claude Code plugin.\n\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Command Options](#command-options)\n- [Required TodoWrite Items](#required-todowrite-items)\n- [Phase 1: Discovery (`tutorial-updates:discovery`)](#phase-1:-discovery-(tutorial-updates:discovery))\n- [Step 1.1: Locate Tutorial Assets](#step-11:-locate-tutorial-assets)\n- [Step 1.2: Parse Manifests](#step-12:-parse-manifests)\n- [Step 1.3: Handle Options](#step-13:-handle-options)\n- [Phase 1.5: Validation (`tutorial-updates:validation`)](#phase-15:-validation-(tutorial-updates:validation))\n- [Step 1.5.1: VHS Syntax Validation](#step-151:-vhs-syntax-validation)\n- [Step 1.5.2: Extract and Validate CLI Commands](#step-152:-extract-and-validate-cli-commands)\n- [Step 1.5.3: Verify Demo Data Exists](#step-153:-verify-demo-data-exists)\n- [Step 1.5.4: Test Commands Locally](#step-154:-test-commands-locally)\n- [Validation Flags](#validation-flags)\n- [Validation Exit Criteria](#validation-exit-criteria)\n- [Phase 1.6: Binary Rebuild (`tutorial-updates:rebuild`)](#phase-16:-binary-rebuild-(tutorial-updates:rebuild))\n- [Step 1.6.1: Detect Build System](#step-161:-detect-build-system)\n- [Step 1.6.2: Check Binary Freshness](#step-162:-check-binary-freshness)\n- [Step 1.6.3: Rebuild Binary](#step-163:-rebuild-binary)\n- [Step 1.6.4: Verify Binary Accessibility](#step-164:-verify-binary-accessibility)\n- [Rebuild Flags](#rebuild-flags)\n- [Rebuild Exit Criteria](#rebuild-exit-criteria)\n- [Phase 2: Recording (`tutorial-updates:recording`)](#phase-2:-recording-(tutorial-updates:recording))\n- [Step 2.1: Process Tape Components](#step-21:-process-tape-components)\n- [Step 2.2: Process Browser Components](#step-22:-process-browser-components)\n- [Step 2.3: Handle Multi-Component Tutorials](#step-23:-handle-multi-component-tutorials)\n- [Phase 3: Generation (`tutorial-updates:generation`)](#phase-3:-generation-(tutorial-updates:generation))\n- [Step 3.1: Parse Tape Annotations](#step-31:-parse-tape-annotations)\n- [Step 3.2: Generate Dual-Tone Markdown](#step-32:-generate-dual-tone-markdown)\n- [Step 3.3: Generate README Demo Section](#step-33:-generate-readme-demo-section)\n- [Demos](#demos)\n- [Quickstart](#quickstart)\n- [Phase 4: Integration (`tutorial-updates:integration`)](#phase-4:-integration-(tutorial-updates:integration))\n- [Step 4.1: Verify All Outputs](#step-41:-verify-all-outputs)\n- [Step 4.2: Update SUMMARY.md (Book)](#step-42:-update-summarymd-(book))\n- [Step 4.3: Report Results](#step-43:-report-results)\n- [Exit Criteria](#exit-criteria)\n- [Error Handling](#error-handling)\n- [Scaffold Mode](#scaffold-mode)\n\n\n# Tutorial Updates Skill\n\nOrchestrate tutorial generation with GIF recordings from VHS tape files and Playwright browser specs.\n\n\n## When To Use\n\n- Generating or updating user-facing tutorials\n- Creating VHS and Playwright tutorial recordings\n\n## When NOT To Use\n\n- Internal documentation without user-facing tutorials\n- API reference docs - use scribe:doc-generator instead\n\n## Overview\n\nThis skill coordinates the complete tutorial generation pipeline:\n\n1. Discover tape files and manifests in the project\n2. Validate tape commands and check binary freshness\n3. Rebuild binaries if stale so demos reflect latest code\n4. Record terminal sessions using VHS (scry:vhs-recording)\n5. Record browser sessions using Playwright (scry:browser-recording)\n6. Generate optimized GIFs (scry:gif-generation)\n7. Compose multi-component tutorials (scry:media-composition)\n8. Generate dual-tone markdown for docs/ and book/\n\n## Command Options\n\n```bash\n/update-tutorial quickstart        # Single tutorial by name\n/update-tutorial sync mcp          # Multiple tutorials\n/update-tutorial --all             # All tutorials with manifests\n/update-tutorial --list            # Show available tutorials\n/update-tutorial --scaffold        # Create structure without recording\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Required TodoWrite Items\n\nCreate todos with these prefixes for progress tracking:\n\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n- tutorial-updates:discovery\n- tutorial-updates:validation\n- tutorial-updates:rebuild\n- tutorial-updates:recording\n- tutorial-updates:generation\n- tutorial-updates:integration\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 1: Discovery (`tutorial-updates:discovery`)\n\n### Step 1.1: Locate Tutorial Assets\n\nFind tape files and manifests in the project:\n\n```bash\n# Find manifest files\nfind . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n\n# Find tape files\nfind . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n\n# Find browser specs\nfind . -name \"*.spec.ts\" -path \"*/browser/*\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.2: Parse Manifests\n\nFor each manifest file, extract:\n- Tutorial name and title\n- Component list (tape files, playwright specs)\n- Output paths for GIFs\n- Composition rules (layout, combine options)\n\nSee `modules/manifest-parsing.md` for manifest schema details.\n\n### Step 1.3: Handle Options\n\n| Option | Behavior |\n|--------|----------|\n| `--list` | Display discovered tutorials and exit |\n| `--all` | Process all discovered manifests |\n| `--scaffold` | Create directory structure and empty files without recording |\n| `<names>` | Process only specified tutorials |\n\nWhen `--list` is specified:\n```\n**Verification:** Run the command with `--help` flag to verify availability.\nAvailable tutorials:\n  quickstart     assets/tapes/quickstart.tape\n  sync           assets/tapes/sync.tape (manifest)\n  mcp            assets/tapes/mcp.manifest.yaml (terminal + browser)\n  skill-debug    assets/tapes/skill-debug.tape\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 1.5: Validation (`tutorial-updates:validation`)\n\n**CRITICAL**: Validate tape commands BEFORE running VHS to avoid expensive regeneration cycles.\n\nSee `modules/tape-validation.md` for detailed validation logic.\n\n### Step 1.5.1: VHS Syntax Validation\n\nCheck each tape file for valid VHS syntax:\n\n```bash\n# Required: Output directive exists\ngrep -q '^Output ' \"$tape_file\" || echo \"ERROR: Missing Output directive\"\n\n# Check for balanced quotes in Type directives\ngrep '^Type ' \"$tape_file\" | while read -r line; do\n  quote_count=$(echo \"$line\" | tr -cd '\"' | wc -c)\n  if [ $((quote_count % 2)) -ne 0 ]; then\n    echo \"ERROR: Unbalanced quotes: $line\"\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.2: Extract and Validate CLI Commands\n\nFor each `Type` directive, extract the command and validate flags:\n\n```bash\n# Extract commands from Type directives\ngrep '^Type ' \"$tape_file\" | sed 's/^Type \"//' | sed 's/\"$//' | while read -r cmd; do\n  # Skip comments, clear, and echo commands\n  [[ \"$cmd\" =~ ^# ]] && continue\n  [[ \"$cmd\" == \"clear\" ]] && continue\n\n  # For skrills commands, validate flags exist\n  if [[ \"$cmd\" =~ ^skrills ]]; then\n    base_cmd=$(echo \"$cmd\" | awk '{print $1, $2}')\n    flags=$(echo \"$cmd\" | grep -oE '\\-\\-[a-zA-Z0-9-]+' || true)\n\n    for flag in $flags; do\n      if ! $base_cmd --help 2>&1 | grep -q -- \"$flag\"; then\n        echo \"ERROR: Invalid flag '$flag' in command: $cmd\"\n      fi\n    done\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.3: Verify Demo Data Exists\n\nIf the tape uses demo data, verify it exists and is populated:\n\n```bash\n# Check SKRILLS_SKILL_DIR if set\nskill_dir=$(grep '^Env SKRILLS_SKILL_DIR' \"$tape_file\" | sed 's/.*\"\\(.*\\)\"/\\1/')\nif [ -n \"$skill_dir\" ]; then\n  if [ ! -d \"$skill_dir\" ]; then\n    echo \"ERROR: Demo skill directory missing: $skill_dir\"\n  else\n    skill_count=$(find \"$skill_dir\" -name \"SKILL.md\" 2>/dev/null | wc -l)\n    if [ \"$skill_count\" -eq 0 ]; then\n      echo \"ERROR: No skills in demo directory: $skill_dir\"\n    else\n      echo \"OK: Found $skill_count demo skills in $skill_dir\"\n    fi\n  fi\nfi\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.4: Test Commands Locally\n\n**CRITICAL**: Run each extracted command locally to verify it produces expected output:\n\n```bash\n# For each command in the tape, do a quick sanity check\n# This catches issues like:\n# - Commands that exit with non-zero status\n# - Commands that produce no output (won't show anything in GIF)\n# - Commands that require user input (will hang VHS)\n\nfor cmd in $(extract_commands \"$tape_file\"); do\n  # Run with timeout to catch hanging commands\n  if ! timeout 5s bash -c \"$cmd\" &>/dev/null; then\n    echo \"WARNING: Command may fail or hang: $cmd\"\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Validation Flags\n\n| Flag | Behavior |\n|------|----------|\n| `--validate-only` | Run validation without generating GIF |\n| `--skip-validation` | Bypass validation for rapid regeneration |\n\n### Validation Exit Criteria\n\n- [ ] VHS tape syntax is valid (Output directive, balanced quotes)\n- [ ] All CLI flags in commands are valid (verified against --help)\n- [ ] Demo data directories exist and are populated\n- [ ] Commands execute successfully with expected output\n\n**If validation fails**: Stop immediately, report errors, and do NOT proceed to VHS recording.\n\n## Phase 1.6: Binary Rebuild (`tutorial-updates:rebuild`)\n\n**CRITICAL**: Ensure the binary being tested in tapes matches the latest source code. Stale binaries produce misleading demos.\n\n### Step 1.6.1: Detect Build System\n\nIdentify the project's build system:\n\n```bash\n# Check for Cargo (Rust)\nif [ -f \"Cargo.toml\" ]; then\n  BUILD_SYSTEM=\"cargo\"\n  BINARY_NAME=$(grep '^name = ' Cargo.toml | head -1 | sed 's/.*\"\\(.*\\)\"/\\1/')\n  echo \"Detected Cargo project: $BINARY_NAME\"\n# Check for Makefile\nelif [ -f \"Makefile\" ]; then\n  BUILD_SYSTEM=\"make\"\n  echo \"Detected Make project\"\n# Unknown\nelse\n  echo \"WARNING: Unknown build system, skipping binary check\"\n  BUILD_SYSTEM=\"unknown\"\nfi\n```\n**Verification:** Run `make --dry-run` to verify build configuration.\n\n### Step 1.6.2: Check Binary Freshness\n\nCompare binary modification time against Git HEAD:\n\n```bash\ncheck_binary_freshness() {\n  local binary_name=\"$1\"\n\n  # Locate binary (check cargo install location first, then PATH)\n  local binary_path=$(which \"$binary_name\" 2>/dev/null)\n\n  if [ -z \"$binary_path\" ]; then\n    echo \"WARNING: Binary '$binary_name' not found in PATH\"\n    return 1\n  fi\n\n  # Get binary modification time (Linux/macOS compatible)\n  local binary_mtime\n  if command -v stat >/dev/null 2>&1; then\n    # Linux\n    binary_mtime=$(stat -c %Y \"$binary_path\" 2>/dev/null || \\\n    # macOS\n    stat -f %m \"$binary_path\" 2>/dev/null)\n  else\n    echo \"WARNING: stat command not available, skipping freshness check\"\n    return 2\n  fi\n\n  # Get Git HEAD commit time\n  local git_head_time=$(git log -1 --format=%ct 2>/dev/null)\n\n  if [ -z \"$git_head_time\" ]; then\n    echo \"WARNING: Not a git repository, skipping freshness check\"\n    return 2\n  fi\n\n  # Compare timestamps\n  if [ \"$binary_mtime\" -lt \"$git_head_time\" ]; then\n    echo \"STALE: Binary is older than Git HEAD\"\n    echo \"  Binary: $(date -d @$binary_mtime 2>/dev/null || date -r $binary_mtime)\"\n    echo \"  HEAD:   $(date -d @$git_head_time 2>/dev/null || date -r $git_head_time)\"\n    return 1\n  else\n    echo \"OK: Binary is up-to-date\"\n    return 0\n  fi\n}\n```\n**Verification:** Run `git status` to confirm working tree state.\n\n### Step 1.6.3: Rebuild Binary\n\nRebuild using the detected build system:\n\n```bash\nrebuild_binary() {\n  local build_system=\"$1\"\n  local binary_name=\"$2\"\n\n  case \"$build_system\" in\n    cargo)\n      echo \"Rebuilding with Cargo...\"\n      # Use cargo install for CLI binaries\n      if [ -d \"crates/cli\" ]; then\n        cargo install --path crates/cli --locked --quiet\n      else\n        cargo install --path . --locked --quiet\n      fi\n      ;;\n    make)\n      echo \"Rebuilding with Make...\"\n      make build --quiet\n      ;;\n    *)\n      echo \"ERROR: Cannot rebuild, unknown build system\"\n      return 1\n      ;;\n  esac\n\n  echo \"Build complete: $binary_name\"\n}\n```\n**Verification:** Run `make --dry-run` to verify build configuration.\n\n### Step 1.6.4: Verify Binary Accessibility\n\nEnsure the rebuilt binary is accessible:\n\n```bash\nverify_binary() {\n  local binary_name=\"$1\"\n\n  if ! command -v \"$binary_name\" >/dev/null 2>&1; then\n    echo \"ERROR: Binary '$binary_name' not found after rebuild\"\n    echo \"  Check PATH includes: $HOME/.cargo/bin\"\n    return 1\n  fi\n\n  # Test binary can execute\n  if ! \"$binary_name\" --version >/dev/null 2>&1; then\n    echo \"WARNING: Binary exists but --version failed\"\n  else\n    echo \"OK: Binary is accessible and functional\"\n    \"$binary_name\" --version\n  fi\n}\n```\n**Verification:** Run `pytest -v` to verify tests pass.\n\n### Rebuild Flags\n\n| Flag | Behavior |\n|------|----------|\n| `--skip-rebuild` | Skip binary freshness check and rebuild |\n| `--force-rebuild` | Force rebuild even if binary is fresh |\n\n### Rebuild Exit Criteria\n\n- [ ] Build system detected (Cargo, Make, or explicitly skipped)\n- [ ] Binary freshness checked against Git HEAD\n- [ ] Binary rebuilt if stale (or forced)\n- [ ] Rebuilt binary is accessible in PATH\n- [ ] Binary executes successfully (--version test)\n\n**If rebuild fails**: Stop immediately, report build errors, and do NOT proceed to tape validation or VHS recording.\n\n## Phase 2: Recording (`tutorial-updates:recording`)\n\n### Step 2.1: Process Tape Components\n\nFor each tape file component:\n\n1. Parse tape file for metadata annotations (@step, @docs-brief, @book-detail)\n2. Validate Output directive exists\n3. Invoke `Skill(scry:vhs-recording)` with tape file path\n4. Verify GIF output was created\n\n### Step 2.2: Process Browser Components\n\nFor each playwright spec component:\n\n1. Check `requires` field for prerequisite commands (e.g., start server)\n2. Launch any required background processes\n3. Invoke `Skill(scry:browser-recording)` with spec path\n4. Stop background processes\n5. Invoke `Skill(scry:gif-generation)` to convert WebM to GIF\n\n### Step 2.3: Handle Multi-Component Tutorials\n\nFor manifests with `combine` section:\n\n1. Verify all component GIFs exist\n2. Invoke `Skill(scry:media-composition)` with manifest\n3. Verify combined output was created\n\n## Phase 3: Generation (`tutorial-updates:generation`)\n\n### Step 3.1: Parse Tape Annotations\n\nExtract documentation content from tape files:\n\n```tape\n# @step Install skrills\n# @docs-brief Install via cargo\n# @book-detail The recommended installation method uses cargo...\nType \"cargo install skrills\"\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\nAnnotations:\n- `@step` - Step title/heading\n- `@docs-brief` - Concise text for project docs (docs/ directory)\n- `@book-detail` - Extended text for technical book (book/ directory)\n\n### Step 3.2: Generate Dual-Tone Markdown\n\nGenerate two versions of each tutorial:\n\n1. **Project docs** (`docs/tutorials/<name>.md`)\n   - Brief, action-oriented\n   - Uses @docs-brief content\n   - Focuses on commands and quick results\n\n2. **Technical book** (`book/src/tutorials/<name>.md`)\n   - Detailed, educational\n   - Uses @book-detail content\n   - Explains concepts and rationale\n\nSee `modules/markdown-generation.md` for formatting details.\n\n### Step 3.3: Generate README Demo Section\n\nCreate or update demo section in README.md:\n\n```markdown\n## Demos\n\n### Quickstart\n![Quickstart demo](assets/gifs/quickstart.gif)\n*Install, validate, analyze, and serve in under a minute. [Full tutorial](docs/tutorials/quickstart.md)*\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 4: Integration (`tutorial-updates:integration`)\n\n### Step 4.1: Verify All Outputs\n\nConfirm all expected files exist:\n\n```bash\n# Check GIF files\nfor gif in assets/gifs/*.gif; do\n  if [[ -f \"$gif\" ]]; then\n    echo \"OK: $gif ($(du -h \"$gif\" | cut -f1))\"\n  else\n    echo \"MISSING: $gif\"\n  fi\ndone\n\n# Check markdown files\nls -la docs/tutorials/*.md 2>/dev/null\nls -la book/src/tutorials/*.md 2>/dev/null\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 4.2: Update SUMMARY.md (Book)\n\nIf the project has an mdBook structure, update `book/src/SUMMARY.md`:\n\n```markdown\n- [Tutorials](./tutorials/README.md)\n  - [Quickstart](./tutorials/quickstart.md)\n  - [Sync Workflow](./tutorials/sync.md)\n  - [MCP Integration](./tutorials/mcp.md)\n  - [Skill Debugging](./tutorials/skill-debug.md)\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 4.3: Report Results\n\nSummarize the update:\n\n```\n**Verification:** Run the command with `--help` flag to verify availability.\nTutorial Update Complete\n========================\nTutorials processed: 4\nGIFs generated: 5\n  - quickstart.gif (1.2MB)\n  - sync.gif (980KB)\n  - mcp-terminal.gif (1.5MB)\n  - mcp-browser.gif (2.1MB)\n  - skill-debug.gif (890KB)\n\nMarkdown generated:\n  - docs/tutorials/ (4 files)\n  - book/src/tutorials/ (4 files)\n\nREADME demo section updated\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Exit Criteria\n\n- [ ] All specified tutorials processed (or all if --all)\n- [ ] GIF files created at manifest-specified paths\n- [ ] Dual-tone markdown generated for each tutorial\n- [ ] README demo section updated with GIF embeds\n- [ ] Book SUMMARY.md updated (if applicable)\n- [ ] All TodoWrite items completed\n\n## Error Handling\n\n| Error | Resolution |\n|-------|------------|\n| VHS not installed | `go install github.com/charmbracelet/vhs@latest` |\n| Playwright not installed | `npm install -D @playwright/test && npx playwright install chromium` |\n| Tape file missing Output | Add `Output assets/gifs/<name>.gif` directive |\n| Browser spec requires server | Start server before running spec |\n| GIF too large | Adjust fps/scale in gif-generation |\n\n## Scaffold Mode\n\nWhen `--scaffold` is specified, create structure without recording:\n\n1. Create `assets/tapes/` directory\n2. Create `assets/gifs/` directory\n3. Create `assets/browser/` directory (if browser tutorials planned)\n4. Create template tape file with metadata annotations\n5. Create template manifest file\n6. Create empty markdown files in docs/tutorials/ and book/src/tutorials/\n\nTemplate tape file:\n```tape\n# @title: Tutorial Name\n# @description: Brief description of the tutorial\n\nOutput assets/gifs/tutorial-name.gif\nSet FontSize 14\nSet Width 1200\nSet Height 600\nSet Theme \"Catppuccin Mocha\"\n\n# @step Step 1 Title\n# @docs-brief Brief docs text\n# @book-detail Extended book text with more context and explanation\nType \"command here\"\nEnter\nSleep 2s\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\nFile v1.9.17:_meta.json\n\n{\n  \"ownerId\": \"kn7d107jg9jv602h9ytsegydq184a42s\",\n  \"slug\": \"nm-sanctum-tutorial-updates\",\n  \"version\": \"1.9.17\",\n  \"publishedAt\": 1785390077600\n}\n\nFile v1.9.17:modules/manifest-parsing.md\n\n# Manifest Parsing Module\n\nParse `.manifest.yaml` files and tape file annotations for tutorial orchestration.\n\n## Manifest Schema\n\nTutorial manifests define multi-component tutorials with composition rules:\n\n```yaml\n# Full manifest schema\nname: string              # Required: identifier for the tutorial\ntitle: string             # Optional: human-readable title\ndescription: string       # Optional: brief description\n\ncomponents:               # Required: list of media components\n  - type: tape            # Component type: tape, playwright, static\n    source: path/to.tape  # Path to source file (relative to manifest)\n    output: path/to.gif   # Path for generated output\n    options:              # Optional: component-specific options\n      fps: 10\n      width: 800\n\n  - type: playwright\n    source: browser/spec.ts\n    output: assets/gifs/browser.gif\n    requires:             # Optional: commands to run before\n      - \"npm run serve\"\n\n  - type: static          # Pre-existing asset (no generation)\n    source: existing.gif\n    output: existing.gif\n\ncombine:                  # Optional: composition rules\n  output: combined.gif    # Path for combined output\n  layout: vertical        # Layout: vertical, horizontal, sequential, grid, pip\n  options:\n    padding: 10\n    background: \"#1a1a2e\"\n```\n\n## Component Types\n\n### Tape Components\n\nVHS tape files for terminal recordings:\n\n```yaml\n- type: tape\n  source: quickstart.tape\n  output: assets/gifs/quickstart.gif\n  options:\n    # Override tape file settings if needed\n    width: 1000\n    height: 500\n```\n\n### Playwright Components\n\nBrowser automation specs:\n\n```yaml\n- type: playwright\n  source: browser/mcp-dashboard.spec.ts\n  output: assets/gifs/mcp-browser.gif\n  requires:\n    - \"skrills serve\"\n  options:\n    fps: 12\n    width: 1280\n```\n\nThe `requires` array specifies commands to run before the spec. These run as background processes and are terminated after recording.\n\n### Static Components\n\nPre-existing assets that don't need generation:\n\n```yaml\n- type: static\n  source: diagrams/architecture.gif\n  output: diagrams/architecture.gif\n```\n\n## Parsing Tape File Annotations\n\nTape files contain inline annotations for documentation generation:\n\n### Annotation Format\n\n```tape\n# @title: Tutorial Title\n# @description: Brief description for README\n\n# @step Step Name\n# @docs-brief Concise text for project docs\n# @book-detail Extended explanation for technical book\nType \"command\"\nEnter\n```\n\n### Annotation Types\n\n| Annotation | Scope | Purpose |\n|------------|-------|---------|\n| `@title` | File | Tutorial title |\n| `@description` | File | Brief description |\n| `@step` | Block | Step heading |\n| `@docs-brief` | Block | Concise docs text |\n| `@book-detail` | Block | Extended book text |\n\n### Parsing Algorithm\n\n```python\ndef parse_tape_annotations(tape_content: str) -> dict:\n    \"\"\"Parse tape file for documentation annotations.\"\"\"\n    result = {\n        \"title\": None,\n        \"description\": None,\n        \"steps\": []\n    }\n\n    current_step = None\n\n    for line in tape_content.splitlines():\n        line = line.strip()\n\n        # File-level annotations\n        if line.startswith(\"# @title:\"):\n            result[\"title\"] = line.split(\":\", 1)[1].strip()\n        elif line.startswith(\"# @description:\"):\n            result[\"description\"] = line.split(\":\", 1)[1].strip()\n\n        # Step-level annotations\n        elif line.startswith(\"# @step\"):\n            # Save previous step\n            if current_step:\n                result[\"steps\"].append(current_step)\n            # Start new step\n            step_name = line.replace(\"# @step\", \"\").strip()\n            current_step = {\n                \"name\": step_name,\n                \"docs_brief\": None,\n                \"book_detail\": None,\n                \"commands\": []\n            }\n        elif line.startswith(\"# @docs-brief\"):\n            if current_step:\n                current_step[\"docs_brief\"] = line.replace(\"# @docs-brief\", \"\").strip()\n        elif line.startswith(\"# @book-detail\"):\n            if current_step:\n                current_step[\"book_detail\"] = line.replace(\"# @book-detail\", \"\").strip()\n\n        # Command lines (Type, Enter, etc.)\n        elif current_step and line.startswith(\"Type\"):\n            # Extract command text\n            match = re.match(r'Type(?:@\\d+ms)?\\s+\"(.+)\"', line)\n            if match:\n                current_step[\"commands\"].append(match.group(1))\n\n    # Don't forget last step\n    if current_step:\n        result[\"steps\"].append(current_step)\n\n    return result\n```\n\n## Manifest Validation\n\n### Required Fields\n\n```bash\n# Validate manifest has required fields\nyq eval '.name' manifest.yaml >/dev/null || echo \"ERROR: missing name\"\nyq eval '.components | length > 0' manifest.yaml | grep -q true || echo \"ERROR: no components\"\n\n# Validate each component\nfor i in $(seq 0 $(($(yq eval '.components | length' manifest.yaml) - 1))); do\n  yq eval \".components[$i].type\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing type\"\n  yq eval \".components[$i].source\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing source\"\n  yq eval \".components[$i].output\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing output\"\ndone\n```\n\n### Source File Validation\n\n```bash\n# Check all source files exist\nfor source in $(yq eval '.components[].source' manifest.yaml); do\n  if [[ ! -f \"$source\" ]]; then\n    echo \"ERROR: Source file not found: $source\"\n  fi\ndone\n```\n\n## Discovery Patterns\n\n### Find All Manifests\n\n```bash\n# Find manifest files in common locations\nfind . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null\nfind assets -name \"*.manifest.yaml\" -type f 2>/dev/null\nfind tutorials -name \"*.manifest.yaml\" -type f 2>/dev/null\n```\n\n### Find Standalone Tape Files\n\nTape files without manifests (single-component tutorials):\n\n```bash\n# Find tape files\nfind . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null\n\n# Filter out those with manifests\nfor tape in $(find . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  manifest=\"${tape%.tape}.manifest.yaml\"\n  if [[ ! -f \"$manifest\" ]]; then\n    echo \"Standalone: $tape\"\n  fi\ndone\n```\n\n### Build Tutorial Index\n\n```bash\n# Create index of all tutorials\necho \"Tutorials:\"\necho \"==========\"\n\n# From manifests\nfor manifest in $(find . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  name=$(yq eval '.name' \"$manifest\")\n  title=$(yq eval '.title // .name' \"$manifest\")\n  components=$(yq eval '.components | length' \"$manifest\")\n  echo \"  $name: $title ($components components) [manifest]\"\ndone\n\n# Standalone tapes\nfor tape in $(find . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  manifest=\"${tape%.tape}.manifest.yaml\"\n  if [[ ! -f \"$manifest\" ]]; then\n    name=$(basename \"$tape\" .tape)\n    echo \"  $name: $tape [standalone]\"\n  fi\ndone\n```\n\n## Error Handling\n\n| Error | Resolution |\n|-------|------------|\n| Manifest parse error | Validate YAML syntax with `yq eval '.' manifest.yaml` |\n| Missing source file | Check path is relative to manifest location |\n| Unknown component type | Use `tape`, `playwright`, or `static` |\n| Missing combine output | Add `combine.output` field if combine section exists |\n| Circular requires | validate background processes don't depend on each other |\n\n## Example Manifests\n\n### Simple Tape Tutorial\n\n```yaml\nname: quickstart\ntitle: \"Quickstart Guide\"\ncomponents:\n  - type: tape\n    source: quickstart.tape\n    output: assets/gifs/quickstart.gif\n```\n\n### Multi-Component Tutorial\n\n```yaml\nname: mcp\ntitle: \"MCP Server Integration\"\ndescription: \"Terminal and browser demo of MCP server\"\ncomponents:\n  - type: tape\n    source: mcp-terminal.tape\n    output: assets/gifs/mcp-terminal.gif\n  - type: playwright\n    source: browser/mcp-dashboard.spec.ts\n    output: assets/gifs/mcp-browser.gif\n    requires:\n      - \"skrills serve\"\ncombine:\n  output: assets/gifs/mcp-combined.gif\n  layout: vertical\n  options:\n    padding: 10\n    background: \"#0d1117\"\n```\n\n### Tutorial with Static Assets\n\n```yaml\nname: architecture\ntitle: \"Architecture Overview\"\ncomponents:\n  - type: tape\n    source: arch-demo.tape\n    output: assets/gifs/arch-demo.gif\n  - type: static\n    source: diagrams/system-overview.gif\n    output: diagrams/system-overview.gif\ncombine:\n  output: assets/gifs/architecture-full.gif\n  layout: sequential\n```\n\nFile v1.9.17:modules/markdown-generation.md\n\n# Markdown Generation Module\n\nGenerate dual-tone markdown documentation from tape file annotations and manifest metadata.\n\n## Dual-Tone System\n\nTutorials are generated in two tones for different audiences:\n\n| Tone | Location | Audience | Style |\n|------|----------|----------|-------|\n| **Project Docs** | `docs/tutorials/` | Users getting started | Concise, action-oriented |\n| **Technical Book** | `book/src/tutorials/` | Developers learning deeply | Detailed, educational |\n\n## Annotation Sources\n\nContent comes from tape file annotations:\n\n```tape\n# @step Install the CLI\n# @docs-brief Install via cargo with a single command\n# @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.\nType \"cargo install skrills\"\n```\n\n- `@docs-brief` - Used for project docs (brief, focused)\n- `@book-detail` - Used for technical book (extended, contextual)\n- If only one is present, use it for both\n- If neither is present, generate minimal text from step name\n\n## Project Docs Format\n\n### Template Structure\n\n```markdown\n# {Tutorial Title}\n\n{Description from @description}\n\n![Demo]({relative-path-to-gif})\n\n## Prerequisites\n\n- Prerequisite 1\n- Prerequisite 2\n\n## Steps\n\n### {Step 1 Name}\n\n{@docs-brief content}\n\n```bash\n{command from Type directive}\n```\n\n### {Step 2 Name}\n\n{@docs-brief content}\n\n```bash\n{command}\n```\n\n## Next Steps\n\n- Link to related tutorial 1\n- Link to related tutorial 2\n```\n\n### Example Output\n\n```markdown\n# Quickstart\n\nInstall, validate, analyze, and serve in under a minute.\n\n![Quickstart demo](../../assets/gifs/quickstart.gif)\n\n## Prerequisites\n\n- Rust toolchain installed (`rustup`)\n- Terminal with UTF-8 support\n\n## Steps\n\n### Install skrills\n\nInstall via cargo with a single command.\n\n```bash\ncargo install skrills\n```\n\n### Validate Skills\n\nValidate and auto-fix missing frontmatter.\n\n```bash\nskrills validate --target codex --autofix\n```\n\n### Analyze Token Usage\n\nAnalyze skills for token optimization opportunities.\n\n```bash\nskrills analyze --min-tokens 500 --suggestions\n```\n\n### Start MCP Server\n\nStart the MCP server.\n\n```bash\nskrills serve\n```\n\n## Next Steps\n\n- [Sync Workflow](./sync.md) - Bidirectional sync between Claude Code and Codex CLI\n- [MCP Integration](./mcp.md) - Use skrills as an MCP server\n```\n\n## Technical Book Format\n\n### Template Structure\n\n```markdown\n# {Tutorial Title}\n\n{Extended description}\n\n## Overview\n\n{Context and learning objectives}\n\n![Demo]({relative-path-to-gif})\n\n## {Step 1 Name}\n\n{@book-detail content - multiple paragraphs allowed}\n\n```bash\n{command from Type directive}\n```\n\n{Additional explanation of what the command does}\n\n## {Step 2 Name}\n\n{@book-detail content}\n\n```bash\n{command}\n```\n\n{Explanation of output and next steps}\n\n## Summary\n\n{Key takeaways}\n\n## Further Reading\n\n- Internal link 1\n- External reference 1\n```\n\n### Example Output\n\n```markdown\n# Quickstart\n\nThis guide walks through the complete skrills workflow: installation, validation, analysis, and serving skills via MCP.\n\n## Overview\n\nWhat this guide covers:\n\n- How to install skrills using cargo\n- The difference between Claude Code and Codex CLI validation targets\n- How to analyze skills for token optimization\n- How to expose skills via the MCP protocol\n\n![Quickstart demo](../assets/gifs/quickstart.gif)\n\n## Install skrills\n\nThe 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.\n\n```bash\ncargo install skrills\n```\n\nThe binary will be placed in `~/.cargo/bin/`, which should be in your PATH if you installed Rust using rustup.\n\n## Validate Skills\n\nSkrills 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.\n\n```bash\nskrills validate --target codex --autofix\n```\n\nThe `--autofix` flag automatically derives missing frontmatter from the file path and content:\n1. Parses the skill filename to derive `name`\n2. Extracts the first paragraph as `description`\n3. Inserts YAML frontmatter at the file start\n\nThis makes migration from Claude Code to Codex straightforward.\n\n## Analyze Token Usage\n\nThe analyzer reports token counts for each skill and suggests optimizations for large skills that may consume excessive context.\n\n```bash\nskrills analyze --min-tokens 500 --suggestions\n```\n\nSkills exceeding the threshold are flagged with specific recommendations:\n- Split into multiple focused skills\n- Extract reusable modules\n- Remove redundant content\n\n## Start MCP Server\n\nWhen running as an MCP server, skrills exposes tools for skill discovery, validation, and analysis to any MCP-compatible client.\n\n```bash\nskrills serve\n```\n\nThe server listens on the default MCP port and responds to tool invocations from Claude Code or other clients.\n\n## Summary\n\n- Install skrills with `cargo install skrills`\n- Validate skills for Codex compatibility with `--target codex`\n- Use `--autofix` to automatically add required frontmatter\n- Analyze token usage to optimize context consumption\n- Serve skills via MCP for integration with AI assistants\n\n## Further Reading\n\n- [Sync Workflow](./sync.md) - Bidirectional synchronization between skill repositories\n- [MCP Integration](./mcp.md) - Advanced MCP server configuration\n- [Skill Debugging](./skill-debug.md) - Troubleshooting skill loading issues\n```\n\n## GIF Embedding\n\n### Relative Path Calculation\n\nGIFs are embedded with paths relative to the markdown file:\n\n| Markdown Location | GIF Location | Relative Path |\n|-------------------|--------------|---------------|\n| `docs/tutorials/quickstart.md` | `assets/gifs/quickstart.gif` | `../../assets/gifs/quickstart.gif` |\n| `book/src/tutorials/quickstart.md` | `assets/gifs/quickstart.gif` | `../../../assets/gifs/quickstart.gif` |\n| `README.md` | `assets/gifs/quickstart.gif` | `assets/gifs/quickstart.gif` |\n\n### Path Calculation Algorithm\n\n```python\ndef relative_gif_path(markdown_path: str, gif_path: str) -> str:\n    \"\"\"Calculate relative path from markdown file to GIF.\"\"\"\n    from pathlib import Path\n\n    md = Path(markdown_path)\n    gif = Path(gif_path)\n\n    # Get common ancestor\n    common = Path(*os.path.commonprefix([md.parts, gif.parts]))\n\n    # Calculate relative path\n    md_depth = len(md.parent.relative_to(common).parts)\n    gif_relative = gif.relative_to(common)\n\n    return \"../\" * md_depth + str(gif_relative)\n```\n\n### Embedding Format\n\n```markdown\n![{Alt text}]({relative-path})\n```\n\nFor multi-component tutorials with combined GIF:\n\n```markdown\n![{Tutorial title} demo]({relative-path-to-combined-gif})\n\n*This demo shows both terminal and browser interactions.*\n```\n\n## README Integration\n\n### Demo Section Template\n\n```markdown\n## Demos\n\n### {Tutorial 1 Title}\n![{Tutorial 1 title} demo]({gif-path})\n*{Description}. [Full tutorial]({docs-tutorial-path})*\n\n### {Tutorial 2 Title}\n![{Tutorial 2 title} demo]({gif-path})\n*{Description}. [Full tutorial]({docs-tutorial-path})*\n```\n\n### Generating README Section\n\n```python\ndef generate_readme_demos(tutorials: list) -> str:\n    \"\"\"Generate demo section for README.\"\"\"\n    lines = [\"## Demos\", \"\"]\n\n    for tutorial in tutorials:\n        lines.extend([\n            f\"### {tutorial['title']}\",\n            f\"![{tutorial['title']} demo]({tutorial['gif_path']})\",\n            f\"*{tutorial['description']}. [Full tutorial]({tutorial['docs_path']})*\",\n            \"\"\n        ])\n\n    return \"\\n\".join(lines)\n```\n\n### Updating README\n\nReplace the existing demo section or append if not present:\n\n```bash\n# Check if demo section exists\nif grep -q \"^## Demos\" README.md; then\n  # Replace section (between ## Demos and next ##)\n  sed -i '/^## Demos/,/^## [^D]/{ /^## [^D]/!d }' README.md\n  # Insert new content after ## Demos\nfi\n```\n\n## Book SUMMARY.md Integration\n\n### Template\n\n```markdown\n- [Tutorials](./tutorials/README.md)\n  - [{Tutorial 1 Title}](./tutorials/{name1}.md)\n  - [{Tutorial 2 Title}](./tutorials/{name2}.md)\n```\n\n### Detection and Update\n\n```bash\n# Check if tutorials section exists in SUMMARY.md\nif [[ -f \"book/src/SUMMARY.md\" ]]; then\n  if grep -q \"Tutorials\" book/src/SUMMARY.md; then\n    echo \"Tutorials section exists - update entries\"\n  else\n    echo \"Add Tutorials section to SUMMARY.md\"\n  fi\nfi\n```\n\n## Content Guidelines\n\n### Project Docs Style\n\n- Action-oriented imperatives: \"Install\", \"Run\", \"Configure\"\n- One paragraph per step maximum\n- Focus on commands and results\n- Minimal explanation of \"why\"\n- Include prerequisites section\n\n### Technical Book Style\n\n- Educational tone: \"This guide explains...\"\n- Multiple paragraphs allowed per step\n- Explain rationale and context\n- Include troubleshooting tips\n- Reference related concepts\n- Add summary and further reading sections\n\n### Common Rules (Both)\n\n- No filler phrases (\"simply\", \"just\", \"easily\")\n- No emojis or decorative elements\n- Grounded, specific language\n- Code blocks for all commands\n- Consistent heading hierarchy\n- Prose text wraps at 80 chars per line (hybrid wrapping:\n  prefer sentence/clause boundaries over arbitrary breaks)\n- Blank line before and after every heading\n- ATX headings only (`#` prefix, no setext underlines)\n- Blank line before every list\n- Reference-style links when inline links push past 80 chars\n- Full formatting spec: `Skill(leyline:markdown-formatting)`\n\n## Error Handling\n\n| Issue | Resolution |\n|-------|------------|\n| Missing @docs-brief | Fall back to @book-detail or step name |\n| Missing @book-detail | Fall back to @docs-brief or generate minimal text |\n| No Output directive | Skip GIF embed, log warning |\n| Invalid path calculation | Verify markdown and GIF paths are relative to project root |\n| README section conflict | Preserve user content outside ## Demos section |\n\nFile v1.9.17:modules/tape-validation.md\n\n# Tape Validation Module\n\nPre-flight validation for VHS tape files before GIF generation. Validate commands work correctly and demo data exists BEFORE running the time-consuming VHS recording.\n\n## Overview\n\nThis module catches errors early:\n- Stale binaries detected before recording (Phase 0)\n- Invalid CLI flags discovered before GIF generation\n- Missing demo data detected before recording starts\n- VHS syntax issues reported before execution\n\n## Validation Phases\n\n### Phase 0: Binary Freshness Check\n\n**CRITICAL**: Verify the CLI binary matches the latest source code. Stale binaries produce misleading demos.\n\n```bash\n# Check if binary is older than Git HEAD\ncheck_binary_freshness() {\n  local binary_name=\"$1\"\n\n  # Locate binary in PATH\n  local binary_path=$(which \"$binary_name\" 2>/dev/null)\n\n  if [ -z \"$binary_path\" ]; then\n    echo \"WARNING: Binary '$binary_name' not found in PATH\"\n    return 1\n  fi\n\n  # Get binary modification time (Linux/macOS compatible)\n  local binary_mtime\n  binary_mtime=$(stat -c %Y \"$binary_path\" 2>/dev/null || \\\n                 stat -f %m \"$binary_path\" 2>/dev/null)\n\n  # Get Git HEAD commit time\n  local git_head_time=$(git log -1 --format=%ct 2>/dev/null)\n\n  if [ -z \"$git_head_time\" ]; then\n    echo \"WARNING: Not a git repository, skipping freshness check\"\n    return 2\n  fi\n\n  # Compare timestamps\n  if [ \"$binary_mtime\" -lt \"$git_head_time\" ]; then\n    echo \"STALE: Binary is older than Git HEAD\"\n    echo \"  Binary: $(date -d @$binary_mtime 2>/dev/null || date -r $binary_mtime)\"\n    echo \"  HEAD:   $(date -d @$git_head_time 2>/dev/null || date -r $git_head_time)\"\n    echo \"\"\n    echo \"RECOMMENDATION: Rebuild binary before recording tape\"\n    echo \"  cargo install --path crates/cli --locked\"\n    return 1\n  else\n    echo \"OK: Binary is up-to-date with Git HEAD\"\n    return 0\n  fi\n}\n```\n\n**Error handling**:\n- Exit code 0: Binary is fresh\n- Exit code 1: Binary is stale (older than HEAD)\n- Exit code 2: Cannot determine (not in git repo or binary not found)\n\n**Integration**:\n```bash\n# Run before tape validation\ncheck_binary_freshness \"skrills\"\nif [ $? -eq 1 ]; then\n  echo \"ERROR: Stale binary detected\"\n  echo \"Run 'cargo install --path crates/cli --locked' to rebuild\"\n  exit 1\nfi\n```\n\n## Command Validation Phases\n\n### 1. VHS Syntax Validation\n\nCheck that the tape file has valid VHS syntax:\n\n```bash\n# Required: Output directive\ngrep -q '^Output ' \"$tape_file\" || echo \"ERROR: Missing Output directive\"\n\n# Check for balanced quotes in Type directives\ngrep '^Type ' \"$tape_file\" | while read -r line; do\n  # Count quotes (should be even)\n  quote_count=$(echo \"$line\" | tr -cd '\"' | wc -c)\n  if [ $((quote_count % 2)) -ne 0 ]; then\n    echo \"ERROR: Unbalanced quotes in: $line\"\n  fi\ndone\n```\n\n**Error examples**:\n- \"Missing Output directive in quickstart.tape:1\"\n- \"Unbalanced quotes in Type directive at quickstart.tape:15\"\n\n### 2. Command Extraction\n\nParse `Type` directives to extract shell commands:\n\n```bash\n# Extract commands from Type directives\nextract_commands() {\n  local tape_file=\"$1\"\n  grep '^Type ' \"$tape_file\" | \\\n    sed 's/^Type \"//' | \\\n    sed 's/\"$//' | \\\n    grep -v '^#' | \\\n    grep -v '^clear$' | \\\n    grep -v '^$'\n}\n```\n\n**Example**:\n```vhs\nType \"skrills validate --errors-only\"\n```\nExtracts: `skrills validate --errors-only`\n\n### 3. CLI Flag Validation\n\nFor each extracted command, validate flags exist:\n\n```bash\nvalidate_command_flags() {\n  local cmd=\"$1\"\n  local line_num=\"$2\"\n\n  # Extract the base command (e.g., \"skrills validate\")\n  local base_cmd=$(echo \"$cmd\" | awk '{print $1, $2}')\n\n  # Get help output for flag discovery\n  local help_output=$($base_cmd --help 2>&1)\n\n  # Extract flags from the command\n  local flags=$(echo \"$cmd\" | grep -oE '\\-\\-[a-zA-Z0-9-]+')\n\n  for flag in $flags; do\n    if ! echo \"$help_output\" | grep -q -- \"$flag\"; then\n      echo \"ERROR: Invalid flag '$flag' at line $line_num\"\n      echo \"  Command: $cmd\"\n      echo \"  Run '$base_cmd --help' to see available flags\"\n    fi\n  done\n}\n```\n\n**Example validation**:\n```bash\n# Command from tape line 12\nskrills create demo --sample basic\n\n# Validation\n$ skrills create --help | grep -- '--sample'\n# (no output - flag doesn't exist)\n\n# Error output\nERROR: Invalid flag '--sample' at line 12\n  Command: skrills create demo --sample basic\n  Run 'skrills create --help' to see available flags\n```\n\n### 4. Demo Data Verification\n\nCheck that demo directories and skills exist:\n\n```bash\nverify_demo_data() {\n  local tape_file=\"$1\"\n\n  # Extract Env SKRILLS_SKILL_DIR if set\n  local skill_dir=$(grep '^Env SKRILLS_SKILL_DIR' \"$tape_file\" | \\\n    sed 's/.*\"\\(.*\\)\"/\\1/')\n\n  if [ -n \"$skill_dir\" ]; then\n    if [ ! -d \"$skill_dir\" ]; then\n      echo \"ERROR: Demo skill directory does not exist: $skill_dir\"\n      return 1\n    fi\n\n    # Check it has content\n    local skill_count=$(find \"$skill_dir\" -name \"SKILL.md\" 2>/dev/null | wc -l)\n    if [ \"$skill_count\" -eq 0 ]; then\n      echo \"ERROR: Demo skill directory is empty: $skill_dir\"\n      echo \"  Expected at least one SKILL.md file\"\n      return 1\n    fi\n\n    echo \"OK: Demo skills found: $skill_count skills in $skill_dir\"\n  fi\n\n  # Check for other referenced directories\n  grep '^Type.*mkdir\\|^Type.*cp\\|^Type.*cd' \"$tape_file\" | while read -r line; do\n    # Extract directory paths and verify parent exists\n    # This is optional - catches obvious path errors\n    true\n  done\n}\n```\n\n### 5. Expected Output Verification\n\nFor commands that should produce visible output, verify they will:\n\n```bash\nverify_expected_output() {\n  local cmd=\"$1\"\n\n  # Skip echo commands (always produce output)\n  echo \"$cmd\" | grep -q '^echo ' && return 0\n\n  # For skrills commands, do a dry-run to verify output\n  if echo \"$cmd\" | grep -q '^skrills '; then\n    # Run with --help to verify command exists\n    local base=$(echo \"$cmd\" | awk '{print $1, $2}')\n    if ! $base --help &>/dev/null; then\n      echo \"WARNING: Command may not produce output: $cmd\"\n    fi\n  fi\n}\n```\n\n## Complete Validation Script\n\n```bash\n#!/bin/bash\n# validate_tape.sh - Pre-flight validation for VHS tape files\n\nvalidate_tape() {\n  local tape_file=\"$1\"\n  local errors=0\n\n  echo \"=== Validating: $tape_file ===\"\n\n  # Phase 1: Syntax\n  echo \"Phase 1: VHS Syntax...\"\n  if ! grep -q '^Output ' \"$tape_file\"; then\n    echo \"  ERROR: Missing Output directive\"\n    ((errors++))\n  else\n    echo \"  OK: Output directive found\"\n  fi\n\n  # Phase 2: Extract commands\n  echo \"Phase 2: Extracting commands...\"\n  local line_num=0\n  local cmd_count=0\n  while IFS= read -r line; do\n    ((line_num++))\n    if [[ \"$line\" =~ ^Type\\ \\\" ]]; then\n      cmd=$(echo \"$line\" | sed 's/^Type \"//' | sed 's/\"$//')\n      ((cmd_count++))\n\n      # Phase 3: Validate CLI flags\n      if [[ \"$cmd\" =~ ^skrills ]]; then\n        base_cmd=$(echo \"$cmd\" | awk '{print $1, $2}')\n        flags=$(echo \"$cmd\" | grep -oE '\\-\\-[a-zA-Z0-9-]+' || true)\n\n        if [ -n \"$flags\" ]; then\n          help_output=$($base_cmd --help 2>&1 || echo \"\")\n          for flag in $flags; do\n            if ! echo \"$help_output\" | grep -q -- \"$flag\"; then\n              echo \"  ERROR: Invalid flag '$flag' at line $line_num\"\n              echo \"         Command: $cmd\"\n              ((errors++))\n            fi\n          done\n        fi\n      fi\n    fi\n  done < \"$tape_file\"\n  echo \"  Extracted $cmd_count commands\"\n\n  # Phase 4: Demo data\n  echo \"Phase 4: Demo data verification...\"\n  skill_dir=$(grep '^Env SKRILLS_SKILL_DIR' \"$tape_file\" | sed 's/.*\"\\(.*\\)\"/\\1/' || true)\n  if [ -n \"$skill_dir\" ]; then\n    if [ ! -d \"$skill_dir\" ]; then\n      echo \"  ERROR: Demo skill directory missing: $skill_dir\"\n      ((errors++))\n    else\n      skill_count=$(find \"$skill_dir\" -name \"SKILL.md\" 2>/dev/null | wc -l)\n      if [ \"$skill_count\" -eq 0 ]; then\n        echo \"  ERROR: No skills in demo directory: $skill_dir\"\n        ((errors++))\n      else\n        echo \"  OK: Found $skill_count demo skills\"\n      fi\n    fi\n  fi\n\n  # Summary\n  echo \"=== Validation Complete ===\"\n  if [ \"$errors\" -eq 0 ]; then\n    echo \"PASSED: No errors found\"\n    return 0\n  else\n    echo \"FAILED: $errors error(s) found\"\n    return 1\n  fi\n}\n\n# Run if called directly\nif [ -n \"$1\" ]; then\n  validate_tape \"$1\"\nfi\n```\n\n## Integration with Workflow\n\nBefore running VHS, execute validation:\n\n```bash\n# In the tutorial-updates skill Phase 1.5\nvalidate_tape \"$tape_file\"\nif [ $? -ne 0 ]; then\n  echo \"Validation failed. Fix errors before running VHS.\"\n  exit 1\nfi\n\n# Proceed to VHS recording\nvhs \"$tape_file\"\n```\n\n## Flags\n\n### `--validate-only`\n\nRun validation without generating GIF:\n\n```bash\n# Validate all tapes without recording\nfor tape in assets/tapes/*.tape; do\n  validate_tape \"$tape\"\ndone\n```\n\n### `--skip-validation`\n\nBypass validation for rapid regeneration:\n\n```bash\n# Skip validation when commands are known-good\nif [ \"$SKIP_VALIDATION\" != \"true\" ]; then\n  validate_tape \"$tape_file\" || exit 1\nfi\nvhs \"$tape_file\"\n```\n\n## Exit Codes\n\n| Code | Meaning |\n|------|---------|\n| 0 | Validation passed |\n| 1 | Validation failed (errors found) |\n| 2 | Validation skipped (--skip-validation) |\n\n## Error Message Format\n\nStandardized format for actionable errors:\n\n```\nERROR: <short-description> at line <number>\n  Command: <full-command>\n  <hint-or-available-options>\n```\n\nExample:\n```\nERROR: Invalid flag '--sample' at line 12\n  Command: skrills validate --sample 5\n  Run 'skrills validate --help' to see available flags\n```\n\nFile v1.9.17:skill-card.md\n\n## Description: <br>\nGenerates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording. <br>\n\nThis skill is ready for commercial/non-commercial use. <br>\n\n## Publisher: <br>\n[athola](https://clawhub.ai/user/athola) <br>\n\n### License/Terms of Use: <br>\nMIT-0 <br>\n\n\n## Use Case: <br>\nDevelopers and documentation maintainers use this skill to refresh user-facing tutorials, validate VHS and Playwright tutorial assets, record GIF demos, and generate concise project docs plus deeper book-style markdown. <br>\n\n### Deployment Geography for Use: <br>\nGlobal <br>\n\n## Known Risks and Mitigations: <br>\nRisk: The skill can run project-supplied commands from tape files and manifest prerequisites. <br>\nMitigation: Use it only on repositories you trust, review tape files and manifest requires commands before execution, and avoid skip-validation options on unfamiliar content. <br>\nRisk: The skill can rebuild or install binaries such as cargo or make targets before recording tutorials. <br>\nMitigation: Confirm build and install commands before allowing the agent to run them, especially when dependencies or generated binaries changed. <br>\nRisk: The skill can edit top-level documentation such as README.md and book/src/SUMMARY.md. <br>\nMitigation: Review documentation diffs before accepting changes so generated tutorials do not introduce misleading or stale guidance. <br>\n\n\n## Reference(s): <br>\n- [ClawHub skill page](https://clawhub.ai/athola/skills/nm-sanctum-tutorial-updates) <br>\n- [OpenClaw homepage](https://github.com/athola/claude-night-market/tree/master/plugins/sanctum) <br>\n- [Manifest Parsing Module](artifact/modules/manifest-parsing.md) <br>\n- [Markdown Generation Module](artifact/modules/markdown-generation.md) <br>\n- [Tape Validation Module](artifact/modules/tape-validation.md) <br>\n\n\n## Skill Output: <br>\n**Output Type(s):** [Markdown, Files, Shell commands, Configuration instructions, Guidance] <br>\n**Output Format:** [Markdown with inline shell commands and generated tutorial asset paths] <br>\n**Output Parameters:** [1D] <br>\n**Other Properties Related to Output:** [May produce or update tutorial markdown, README demo sections, mdBook summaries, and GIF recording assets.] <br>\n\n## Skill Version(s): <br>\n1.9.17 (source: server release metadata) <br>\n\n## Ethical Considerations: <br>\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment. <br>\n\nArchive v1.9.16: 6 files, 18321 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10011b), modules/tape-validation.md (9405b), skill-card.md (2535b), SKILL.md (20046b), _meta.json (147b)\n\nFile v1.9.16:SKILL.md\n\n---\nname: tutorial-updates\ndescription: |\n  Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording\nversion: 1.9.8\ntriggers:\n  - tutorial\n  - gif\n  - vhs\n  - playwright\n  - documentation\n  - demo\n  - tutorial assets need refreshing\nmetadata: {\"openclaw\": {\"homepage\": \"https://github.com/athola/claude-night-market/tree/master/plugins/sanctum\", \"emoji\": \"\\ud83e\\udd9e\", \"requires\": {\"config\": [\"night-market.sanctum:shared\", \"night-market.sanctum:git-workspace-review\", \"night-market.scry:vhs-recording\", \"night-market.scry:browser-recording\", \"night-market.scry:gif-generation\", \"night-market.scry:media-composition\"]}}}\nsource: claude-night-market\nsource_plugin: sanctum\n---\n\n> **Night Market Skill** — ported from [claude-night-market/sanctum](https://github.com/athola/claude-night-market/tree/master/plugins/sanctum). For the full experience with agents, hooks, and commands, install the Claude Code plugin.\n\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Command Options](#command-options)\n- [Required TodoWrite Items](#required-todowrite-items)\n- [Phase 1: Discovery (`tutorial-updates:discovery`)](#phase-1:-discovery-(tutorial-updates:discovery))\n- [Step 1.1: Locate Tutorial Assets](#step-11:-locate-tutorial-assets)\n- [Step 1.2: Parse Manifests](#step-12:-parse-manifests)\n- [Step 1.3: Handle Options](#step-13:-handle-options)\n- [Phase 1.5: Validation (`tutorial-updates:validation`)](#phase-15:-validation-(tutorial-updates:validation))\n- [Step 1.5.1: VHS Syntax Validation](#step-151:-vhs-syntax-validation)\n- [Step 1.5.2: Extract and Validate CLI Commands](#step-152:-extract-and-validate-cli-commands)\n- [Step 1.5.3: Verify Demo Data Exists](#step-153:-verify-demo-data-exists)\n- [Step 1.5.4: Test Commands Locally](#step-154:-test-commands-locally)\n- [Validation Flags](#validation-flags)\n- [Validation Exit Criteria](#validation-exit-criteria)\n- [Phase 1.6: Binary Rebuild (`tutorial-updates:rebuild`)](#phase-16:-binary-rebuild-(tutorial-updates:rebuild))\n- [Step 1.6.1: Detect Build System](#step-161:-detect-build-system)\n- [Step 1.6.2: Check Binary Freshness](#step-162:-check-binary-freshness)\n- [Step 1.6.3: Rebuild Binary](#step-163:-rebuild-binary)\n- [Step 1.6.4: Verify Binary Accessibility](#step-164:-verify-binary-accessibility)\n- [Rebuild Flags](#rebuild-flags)\n- [Rebuild Exit Criteria](#rebuild-exit-criteria)\n- [Phase 2: Recording (`tutorial-updates:recording`)](#phase-2:-recording-(tutorial-updates:recording))\n- [Step 2.1: Process Tape Components](#step-21:-process-tape-components)\n- [Step 2.2: Process Browser Components](#step-22:-process-browser-components)\n- [Step 2.3: Handle Multi-Component Tutorials](#step-23:-handle-multi-component-tutorials)\n- [Phase 3: Generation (`tutorial-updates:generation`)](#phase-3:-generation-(tutorial-updates:generation))\n- [Step 3.1: Parse Tape Annotations](#step-31:-parse-tape-annotations)\n- [Step 3.2: Generate Dual-Tone Markdown](#step-32:-generate-dual-tone-markdown)\n- [Step 3.3: Generate README Demo Section](#step-33:-generate-readme-demo-section)\n- [Demos](#demos)\n- [Quickstart](#quickstart)\n- [Phase 4: Integration (`tutorial-updates:integration`)](#phase-4:-integration-(tutorial-updates:integration))\n- [Step 4.1: Verify All Outputs](#step-41:-verify-all-outputs)\n- [Step 4.2: Update SUMMARY.md (Book)](#step-42:-update-summarymd-(book))\n- [Step 4.3: Report Results](#step-43:-report-results)\n- [Exit Criteria](#exit-criteria)\n- [Error Handling](#error-handling)\n- [Scaffold Mode](#scaffold-mode)\n\n\n# Tutorial Updates Skill\n\nOrchestrate tutorial generation with GIF recordings from VHS tape files and Playwright browser specs.\n\n\n## When To Use\n\n- Generating or updating user-facing tutorials\n- Creating VHS and Playwright tutorial recordings\n\n## When NOT To Use\n\n- Internal documentation without user-facing tutorials\n- API reference docs - use scribe:doc-generator instead\n\n## Overview\n\nThis skill coordinates the complete tutorial generation pipeline:\n\n1. Discover tape files and manifests in the project\n2. Validate tape commands and check binary freshness\n3. Rebuild binaries if stale so demos reflect latest code\n4. Record terminal sessions using VHS (scry:vhs-recording)\n5. Record browser sessions using Playwright (scry:browser-recording)\n6. Generate optimized GIFs (scry:gif-generation)\n7. Compose multi-component tutorials (scry:media-composition)\n8. Generate dual-tone markdown for docs/ and book/\n\n## Command Options\n\n```bash\n/update-tutorial quickstart        # Single tutorial by name\n/update-tutorial sync mcp          # Multiple tutorials\n/update-tutorial --all             # All tutorials with manifests\n/update-tutorial --list            # Show available tutorials\n/update-tutorial --scaffold        # Create structure without recording\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Required TodoWrite Items\n\nCreate todos with these prefixes for progress tracking:\n\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n- tutorial-updates:discovery\n- tutorial-updates:validation\n- tutorial-updates:rebuild\n- tutorial-updates:recording\n- tutorial-updates:generation\n- tutorial-updates:integration\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 1: Discovery (`tutorial-updates:discovery`)\n\n### Step 1.1: Locate Tutorial Assets\n\nFind tape files and manifests in the project:\n\n```bash\n# Find manifest files\nfind . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n\n# Find tape files\nfind . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n\n# Find browser specs\nfind . -name \"*.spec.ts\" -path \"*/browser/*\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.2: Parse Manifests\n\nFor each manifest file, extract:\n- Tutorial name and title\n- Component list (tape files, playwright specs)\n- Output paths for GIFs\n- Composition rules (layout, combine options)\n\nSee `modules/manifest-parsing.md` for manifest schema details.\n\n### Step 1.3: Handle Options\n\n| Option | Behavior |\n|--------|----------|\n| `--list` | Display discovered tutorials and exit |\n| `--all` | Process all discovered manifests |\n| `--scaffold` | Create directory structure and empty files without recording |\n| `<names>` | Process only specified tutorials |\n\nWhen `--list` is specified:\n```\n**Verification:** Run the command with `--help` flag to verify availability.\nAvailable tutorials:\n  quickstart     assets/tapes/quickstart.tape\n  sync           assets/tapes/sync.tape (manifest)\n  mcp            assets/tapes/mcp.manifest.yaml (terminal + browser)\n  skill-debug    assets/tapes/skill-debug.tape\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 1.5: Validation (`tutorial-updates:validation`)\n\n**CRITICAL**: Validate tape commands BEFORE running VHS to avoid expensive regeneration cycles.\n\nSee `modules/tape-validation.md` for detailed validation logic.\n\n### Step 1.5.1: VHS Syntax Validation\n\nCheck each tape file for valid VHS syntax:\n\n```bash\n# Required: Output directive exists\ngrep -q '^Output ' \"$tape_file\" || echo \"ERROR: Missing Output directive\"\n\n# Check for balanced quotes in Type directives\ngrep '^Type ' \"$tape_file\" | while read -r line; do\n  quote_count=$(echo \"$line\" | tr -cd '\"' | wc -c)\n  if [ $((quote_count % 2)) -ne 0 ]; then\n    echo \"ERROR: Unbalanced quotes: $line\"\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.2: Extract and Validate CLI Commands\n\nFor each `Type` directive, extract the command and validate flags:\n\n```bash\n# Extract commands from Type directives\ngrep '^Type ' \"$tape_file\" | sed 's/^Type \"//' | sed 's/\"$//' | while read -r cmd; do\n  # Skip comments, clear, and echo commands\n  [[ \"$cmd\" =~ ^# ]] && continue\n  [[ \"$cmd\" == \"clear\" ]] && continue\n\n  # For skrills commands, validate flags exist\n  if [[ \"$cmd\" =~ ^skrills ]]; then\n    base_cmd=$(echo \"$cmd\" | awk '{print $1, $2}')\n    flags=$(echo \"$cmd\" | grep -oE '\\-\\-[a-zA-Z0-9-]+' || true)\n\n    for flag in $flags; do\n      if ! $base_cmd --help 2>&1 | grep -q -- \"$flag\"; then\n        echo \"ERROR: Invalid flag '$flag' in command: $cmd\"\n      fi\n    done\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.3: Verify Demo Data Exists\n\nIf the tape uses demo data, verify it exists and is populated:\n\n```bash\n# Check SKRILLS_SKILL_DIR if set\nskill_dir=$(grep '^Env SKRILLS_SKILL_DIR' \"$tape_file\" | sed 's/.*\"\\(.*\\)\"/\\1/')\nif [ -n \"$skill_dir\" ]; then\n  if [ ! -d \"$skill_dir\" ]; then\n    echo \"ERROR: Demo skill directory missing: $skill_dir\"\n  else\n    skill_count=$(find \"$skill_dir\" -name \"SKILL.md\" 2>/dev/null | wc -l)\n    if [ \"$skill_count\" -eq 0 ]; then\n      echo \"ERROR: No skills in demo directory: $skill_dir\"\n    else\n      echo \"OK: Found $skill_count demo skills in $skill_dir\"\n    fi\n  fi\nfi\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 1.5.4: Test Commands Locally\n\n**CRITICAL**: Run each extracted command locally to verify it produces expected output:\n\n```bash\n# For each command in the tape, do a quick sanity check\n# This catches issues like:\n# - Commands that exit with non-zero status\n# - Commands that produce no output (won't show anything in GIF)\n# - Commands that require user input (will hang VHS)\n\nfor cmd in $(extract_commands \"$tape_file\"); do\n  # Run with timeout to catch hanging commands\n  if ! timeout 5s bash -c \"$cmd\" &>/dev/null; then\n    echo \"WARNING: Command may fail or hang: $cmd\"\n  fi\ndone\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Validation Flags\n\n| Flag | Behavior |\n|------|----------|\n| `--validate-only` | Run validation without generating GIF |\n| `--skip-validation` | Bypass validation for rapid regeneration |\n\n### Validation Exit Criteria\n\n- [ ] VHS tape syntax is valid (Output directive, balanced quotes)\n- [ ] All CLI flags in commands are valid (verified against --help)\n- [ ] Demo data directories exist and are populated\n- [ ] Commands execute successfully with expected output\n\n**If validation fails**: Stop immediately, report errors, and do NOT proceed to VHS recording.\n\n## Phase 1.6: Binary Rebuild (`tutorial-updates:rebuild`)\n\n**CRITICAL**: Ensure the binary being tested in tapes matches the latest source code. Stale binaries produce misleading demos.\n\n### Step 1.6.1: Detect Build System\n\nIdentify the project's build system:\n\n```bash\n# Check for Cargo (Rust)\nif [ -f \"Cargo.toml\" ]; then\n  BUILD_SYSTEM=\"cargo\"\n  BINARY_NAME=$(grep '^name = ' Cargo.toml | head -1 | sed 's/.*\"\\(.*\\)\"/\\1/')\n  echo \"Detected Cargo project: $BINARY_NAME\"\n# Check for Makefile\nelif [ -f \"Makefile\" ]; then\n  BUILD_SYSTEM=\"make\"\n  echo \"Detected Make project\"\n# Unknown\nelse\n  echo \"WARNING: Unknown build system, skipping binary check\"\n  BUILD_SYSTEM=\"unknown\"\nfi\n```\n**Verification:** Run `make --dry-run` to verify build configuration.\n\n### Step 1.6.2: Check Binary Freshness\n\nCompare binary modification time against Git HEAD:\n\n```bash\ncheck_binary_freshness() {\n  local binary_name=\"$1\"\n\n  # Locate binary (check cargo install location first, then PATH)\n  local binary_path=$(which \"$binary_name\" 2>/dev/null)\n\n  if [ -z \"$binary_path\" ]; then\n    echo \"WARNING: Binary '$binary_name' not found in PATH\"\n    return 1\n  fi\n\n  # Get binary modification time (Linux/macOS compatible)\n  local binary_mtime\n  if command -v stat >/dev/null 2>&1; then\n    # Linux\n    binary_mtime=$(stat -c %Y \"$binary_path\" 2>/dev/null || \\\n    # macOS\n    stat -f %m \"$binary_path\" 2>/dev/null)\n  else\n    echo \"WARNING: stat command not available, skipping freshness check\"\n    return 2\n  fi\n\n  # Get Git HEAD commit time\n  local git_head_time=$(git log -1 --format=%ct 2>/dev/null)\n\n  if [ -z \"$git_head_time\" ]; then\n    echo \"WARNING: Not a git repository, skipping freshness check\"\n    return 2\n  fi\n\n  # Compare timestamps\n  if [ \"$binary_mtime\" -lt \"$git_head_time\" ]; then\n    echo \"STALE: Binary is older than Git HEAD\"\n    echo \"  Binary: $(date -d @$binary_mtime 2>/dev/null || date -r $binary_mtime)\"\n    echo \"  HEAD:   $(date -d @$git_head_time 2>/dev/null || date -r $git_head_time)\"\n    return 1\n  else\n    echo \"OK: Binary is up-to-date\"\n    return 0\n  fi\n}\n```\n**Verification:** Run `git status` to confirm working tree state.\n\n### Step 1.6.3: Rebuild Binary\n\nRebuild using the detected build system:\n\n```bash\nrebuild_binary() {\n  local build_system=\"$1\"\n  local binary_name=\"$2\"\n\n  case \"$build_system\" in\n    cargo)\n      echo \"Rebuilding with Cargo...\"\n      # Use cargo install for CLI binaries\n      if [ -d \"crates/cli\" ]; then\n        cargo install --path crates/cli --locked --quiet\n      else\n        cargo install --path . --locked --quiet\n      fi\n      ;;\n    make)\n      echo \"Rebuilding with Make...\"\n      make build --quiet\n      ;;\n    *)\n      echo \"ERROR: Cannot rebuild, unknown build system\"\n      return 1\n      ;;\n  esac\n\n  echo \"Build complete: $binary_name\"\n}\n```\n**Verification:** Run `make --dry-run` to verify build configuration.\n\n### Step 1.6.4: Verify Binary Accessibility\n\nEnsure the rebuilt binary is accessible:\n\n```bash\nverify_binary() {\n  local binary_name=\"$1\"\n\n  if ! command -v \"$binary_name\" >/dev/null 2>&1; then\n    echo \"ERROR: Binary '$binary_name' not found after rebuild\"\n    echo \"  Check PATH includes: $HOME/.cargo/bin\"\n    return 1\n  fi\n\n  # Test binary can execute\n  if ! \"$binary_name\" --version >/dev/null 2>&1; then\n    echo \"WARNING: Binary exists but --version failed\"\n  else\n    echo \"OK: Binary is accessible and functional\"\n    \"$binary_name\" --version\n  fi\n}\n```\n**Verification:** Run `pytest -v` to verify tests pass.\n\n### Rebuild Flags\n\n| Flag | Behavior |\n|------|----------|\n| `--skip-rebuild` | Skip binary freshness check and rebuild |\n| `--force-rebuild` | Force rebuild even if binary is fresh |\n\n### Rebuild Exit Criteria\n\n- [ ] Build system detected (Cargo, Make, or explicitly skipped)\n- [ ] Binary freshness checked against Git HEAD\n- [ ] Binary rebuilt if stale (or forced)\n- [ ] Rebuilt binary is accessible in PATH\n- [ ] Binary executes successfully (--version test)\n\n**If rebuild fails**: Stop immediately, report build errors, and do NOT proceed to tape validation or VHS recording.\n\n## Phase 2: Recording (`tutorial-updates:recording`)\n\n### Step 2.1: Process Tape Components\n\nFor each tape file component:\n\n1. Parse tape file for metadata annotations (@step, @docs-brief, @book-detail)\n2. Validate Output directive exists\n3. Invoke `Skill(scry:vhs-recording)` with tape file path\n4. Verify GIF output was created\n\n### Step 2.2: Process Browser Components\n\nFor each playwright spec component:\n\n1. Check `requires` field for prerequisite commands (e.g., start server)\n2. Launch any required background processes\n3. Invoke `Skill(scry:browser-recording)` with spec path\n4. Stop background processes\n5. Invoke `Skill(scry:gif-generation)` to convert WebM to GIF\n\n### Step 2.3: Handle Multi-Component Tutorials\n\nFor manifests with `combine` section:\n\n1. Verify all component GIFs exist\n2. Invoke `Skill(scry:media-composition)` with manifest\n3. Verify combined output was created\n\n## Phase 3: Generation (`tutorial-updates:generation`)\n\n### Step 3.1: Parse Tape Annotations\n\nExtract documentation content from tape files:\n\n```tape\n# @step Install skrills\n# @docs-brief Install via cargo\n# @book-detail The recommended installation method uses cargo...\nType \"cargo install skrills\"\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\nAnnotations:\n- `@step` - Step title/heading\n- `@docs-brief` - Concise text for project docs (docs/ directory)\n- `@book-detail` - Extended text for technical book (book/ directory)\n\n### Step 3.2: Generate Dual-Tone Markdown\n\nGenerate two versions of each tutorial:\n\n1. **Project docs** (`docs/tutorials/<name>.md`)\n   - Brief, action-oriented\n   - Uses @docs-brief content\n   - Focuses on commands and quick results\n\n2. **Technical book** (`book/src/tutorials/<name>.md`)\n   - Detailed, educational\n   - Uses @book-detail content\n   - Explains concepts and rationale\n\nSee `modules/markdown-generation.md` for formatting details.\n\n### Step 3.3: Generate README Demo Section\n\nCreate or update demo section in README.md:\n\n```markdown\n## Demos\n\n### Quickstart\n![Quickstart demo](assets/gifs/quickstart.gif)\n*Install, validate, analyze, and serve in under a minute. [Full tutorial](docs/tutorials/quickstart.md)*\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Phase 4: Integration (`tutorial-updates:integration`)\n\n### Step 4.1: Verify All Outputs\n\nConfirm all expected files exist:\n\n```bash\n# Check GIF files\nfor gif in assets/gifs/*.gif; do\n  if [[ -f \"$gif\" ]]; then\n    echo \"OK: $gif ($(du -h \"$gif\" | cut -f1))\"\n  else\n    echo \"MISSING: $gif\"\n  fi\ndone\n\n# Check markdown files\nls -la docs/tutorials/*.md 2>/dev/null\nls -la book/src/tutorials/*.md 2>/dev/null\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 4.2: Update SUMMARY.md (Book)\n\nIf the project has an mdBook structure, update `book/src/SUMMARY.md`:\n\n```markdown\n- [Tutorials](./tutorials/README.md)\n  - [Quickstart](./tutorials/quickstart.md)\n  - [Sync Workflow](./tutorials/sync.md)\n  - [MCP Integration](./tutorials/mcp.md)\n  - [Skill Debugging](./tutorials/skill-debug.md)\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n### Step 4.3: Report Results\n\nSummarize the update:\n\n```\n**Verification:** Run the command with `--help` flag to verify availability.\nTutorial Update Complete\n========================\nTutorials processed: 4\nGIFs generated: 5\n  - quickstart.gif (1.2MB)\n  - sync.gif (980KB)\n  - mcp-terminal.gif (1.5MB)\n  - mcp-browser.gif (2.1MB)\n  - skill-debug.gif (890KB)\n\nMarkdown generated:\n  - docs/tutorials/ (4 files)\n  - book/src/tutorials/ (4 files)\n\nREADME demo section updated\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\n## Exit Criteria\n\n- [ ] All specified tutorials processed (or all if --all)\n- [ ] GIF files created at manifest-specified paths\n- [ ] Dual-tone markdown generated for each tutorial\n- [ ] README demo section updated with GIF embeds\n- [ ] Book SUMMARY.md updated (if applicable)\n- [ ] All TodoWrite items completed\n\n## Error Handling\n\n| Error | Resolution |\n|-------|------------|\n| VHS not installed | `go install github.com/charmbracelet/vhs@latest` |\n| Playwright not installed | `npm install -D @playwright/test && npx playwright install chromium` |\n| Tape file missing Output | Add `Output assets/gifs/<name>.gif` directive |\n| Browser spec requires server | Start server before running spec |\n| GIF too large | Adjust fps/scale in gif-generation |\n\n## Scaffold Mode\n\nWhen `--scaffold` is specified, create structure without recording:\n\n1. Create `assets/tapes/` directory\n2. Create `assets/gifs/` directory\n3. Create `assets/browser/` directory (if browser tutorials planned)\n4. Create template tape file with metadata annotations\n5. Create template manifest file\n6. Create empty markdown files in docs/tutorials/ and book/src/tutorials/\n\nTemplate tape file:\n```tape\n# @title: Tutorial Name\n# @description: Brief description of the tutorial\n\nOutput assets/gifs/tutorial-name.gif\nSet FontSize 14\nSet Width 1200\nSet Height 600\nSet Theme \"Catppuccin Mocha\"\n\n# @step Step 1 Title\n# @docs-brief Brief docs text\n# @book-detail Extended book text with more context and explanation\nType \"command here\"\nEnter\nSleep 2s\n```\n**Verification:** Run the command with `--help` flag to verify availability.\n\nFile v1.9.16:_meta.json\n\n{\n  \"ownerId\": \"kn7d107jg9jv602h9ytsegydq184a42s\",\n  \"slug\": \"nm-sanctum-tutorial-updates\",\n  \"version\": \"1.9.16\",\n  \"publishedAt\": 1784059085764\n}\n\nFile v1.9.16:modules/manifest-parsing.md\n\n# Manifest Parsing Module\n\nParse `.manifest.yaml` files and tape file annotations for tutorial orchestration.\n\n## Manifest Schema\n\nTutorial manifests define multi-component tutorials with composition rules:\n\n```yaml\n# Full manifest schema\nname: string              # Required: identifier for the tutorial\ntitle: string             # Optional: human-readable title\ndescription: string       # Optional: brief description\n\ncomponents:               # Required: list of media components\n  - type: tape            # Component type: tape, playwright, static\n    source: path/to.tape  # Path to source file (relative to manifest)\n    output: path/to.gif   # Path for generated output\n    options:              # Optional: component-specific options\n      fps: 10\n      width: 800\n\n  - type: playwright\n    source: browser/spec.ts\n    output: assets/gifs/browser.gif\n    requires:             # Optional: commands to run before\n      - \"npm run serve\"\n\n  - type: static          # Pre-existing asset (no generation)\n    source: existing.gif\n    output: existing.gif\n\ncombine:                  # Optional: composition rules\n  output: combined.gif    # Path for combined output\n  layout: vertical        # Layout: vertical, horizontal, sequential, grid, pip\n  options:\n    padding: 10\n    background: \"#1a1a2e\"\n```\n\n## Component Types\n\n### Tape Components\n\nVHS tape files for terminal recordings:\n\n```yaml\n- type: tape\n  source: quickstart.tape\n  output: assets/gifs/quickstart.gif\n  options:\n    # Override tape file settings if needed\n    width: 1000\n    height: 500\n```\n\n### Playwright Components\n\nBrowser automation specs:\n\n```yaml\n- type: playwright\n  source: browser/mcp-dashboard.spec.ts\n  output: assets/gifs/mcp-browser.gif\n  requires:\n    - \"skrills serve\"\n  options:\n    fps: 12\n    width: 1280\n```\n\nThe `requires` array specifies commands to run before the spec. These run as background processes and are terminated after recording.\n\n### Static Components\n\nPre-existing assets that don't need generation:\n\n```yaml\n- type: static\n  source: diagrams/architecture.gif\n  output: diagrams/architecture.gif\n```\n\n## Parsing Tape File Annotations\n\nTape files contain inline annotations for documentation generation:\n\n### Annotation Format\n\n```tape\n# @title: Tutorial Title\n# @description: Brief description for README\n\n# @step Step Name\n# @docs-brief Concise text for project docs\n# @book-detail Extended explanation for technical book\nType \"command\"\nEnter\n```\n\n### Annotation Types\n\n| Annotation | Scope | Purpose |\n|------------|-------|---------|\n| `@title` | File | Tutorial title |\n| `@description` | File | Brief description |\n| `@step` | Block | Step heading |\n| `@docs-brief` | Block | Concise docs text |\n| `@book-detail` | Block | Extended book text |\n\n### Parsing Algorithm\n\n```python\ndef parse_tape_annotations(tape_content: str) -> dict:\n    \"\"\"Parse tape file for documentation annotations.\"\"\"\n    result = {\n        \"title\": None,\n        \"description\": None,\n        \"steps\": []\n    }\n\n    current_step = None\n\n    for line in tape_content.splitlines():\n        line = line.strip()\n\n        # File-level annotations\n        if line.startswith(\"# @title:\"):\n            result[\"title\"] = line.split(\":\", 1)[1].strip()\n        elif line.startswith(\"# @description:\"):\n            result[\"description\"] = line.split(\":\", 1)[1].strip()\n\n        # Step-level annotations\n        elif line.startswith(\"# @step\"):\n            # Save previous step\n            if current_step:\n                result[\"steps\"].append(current_step)\n            # Start new step\n            step_name = line.replace(\"# @step\", \"\").strip()\n            current_step = {\n                \"name\": step_name,\n                \"docs_brief\": None,\n                \"book_detail\": None,\n                \"commands\": []\n            }\n        elif line.startswith(\"# @docs-brief\"):\n            if current_step:\n                current_step[\"docs_brief\"] = line.replace(\"# @docs-brief\", \"\").strip()\n        elif line.startswith(\"# @book-detail\"):\n            if current_step:\n                current_step[\"book_detail\"] = line.replace(\"# @book-detail\", \"\").strip()\n\n        # Command lines (Type, Enter, etc.)\n        elif current_step and line.startswith(\"Type\"):\n            # Extract command text\n            match = re.match(r'Type(?:@\\d+ms)?\\s+\"(.+)\"', line)\n            if match:\n                current_step[\"commands\"].append(match.group(1))\n\n    # Don't forget last step\n    if current_step:\n        result[\"steps\"].append(current_step)\n\n    return result\n```\n\n## Manifest Validation\n\n### Required Fields\n\n```bash\n# Validate manifest has required fields\nyq eval '.name' manifest.yaml >/dev/null || echo \"ERROR: missing name\"\nyq eval '.components | length > 0' manifest.yaml | grep -q true || echo \"ERROR: no components\"\n\n# Validate each component\nfor i in $(seq 0 $(($(yq eval '.components | length' manifest.yaml) - 1))); do\n  yq eval \".components[$i].type\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing type\"\n  yq eval \".components[$i].source\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing source\"\n  yq eval \".components[$i].output\" manifest.yaml >/dev/null || echo \"ERROR: component $i missing output\"\ndone\n```\n\n### Source File Validation\n\n```bash\n# Check all source files exist\nfor source in $(yq eval '.components[].source' manifest.yaml); do\n  if [[ ! -f \"$source\" ]]; then\n    echo \"ERROR: Source file not found: $source\"\n  fi\ndone\n```\n\n## Discovery Patterns\n\n### Find All Manifests\n\n```bash\n# Find manifest files in common locations\nfind . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null\nfind assets -name \"*.manifest.yaml\" -type f 2>/dev/null\nfind tutorials -name \"*.manifest.yaml\" -type f 2>/dev/null\n```\n\n### Find Standalone Tape Files\n\nTape files without manifests (single-component tutorials):\n\n```bash\n# Find tape files\nfind . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null\n\n# Filter out those with manifests\nfor tape in $(find . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  manifest=\"${tape%.tape}.manifest.yaml\"\n  if [[ ! -f \"$manifest\" ]]; then\n    echo \"Standalone: $tape\"\n  fi\ndone\n```\n\n### Build Tutorial Index\n\n```bash\n# Create index of all tutorials\necho \"Tutorials:\"\necho \"==========\"\n\n# From manifests\nfor manifest in $(find . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  name=$(yq eval '.name' \"$manifest\")\n  title=$(yq eval '.title // .name' \"$manifest\")\n  components=$(yq eval '.components | length' \"$manifest\")\n  echo \"  $name: $title ($components components) [manifest]\"\ndone\n\n# Standalone tapes\nfor tape in $(find . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null); do\n  manifest=\"${tape%.tape}.manifest.yaml\"\n  if [[ ! -f \"$manifest\" ]]; then\n    name=$(basename \"$tape\" .tape)\n    echo \"  $name: $tape [standalone]\"\n  fi\ndone\n```\n\n## Error Handling\n\n| Error | Resolution |\n|-------|------------|\n| Manifest parse error | Validate YAML syntax with `yq eval '.' manifest.yaml` |\n| Missing source file | Check path is relative to manifest location |\n| Unknown component type | Use `tape`, `playwright`, or `static` |\n| Missing combine output | Add `combine.output` field if combine section exists |\n| Circular requires | validate background processes don't depend on each other |\n\n## Example Manifests\n\n### Simple Tape Tutorial\n\n```yaml\nname: quickstart\ntitle: \"Quickstart Guide\"\ncomponents:\n  - type: tape\n    source: quickstart.tape\n    output: assets/gifs/quickstart.gif\n```\n\n### Multi-Component Tutorial\n\n```yaml\nname: mcp\ntitle: \"MCP Server Integration\"\ndescription: \"Terminal and browser demo of MCP server\"\ncomponents:\n  - type: tape\n    source: mcp-terminal.tape\n    output: assets/gifs/mcp-terminal.gif\n  - type: playwright\n    source: browser/mcp-dashboard.spec.ts\n    output: assets/gifs/mcp-browser.gif\n    requires:\n      - \"skrills serve\"\ncombine:\n  output: assets/gifs/mcp-combined.gif\n  layout: vertical\n  options:\n    padding: 10\n    background: \"#0d1117\"\n```\n\n### Tutorial with Static Assets\n\n```yaml\nname: architecture\ntitle: \"Architecture Overview\"\ncomponents:\n  - type: tape\n    source: arch-demo.tape\n    output: assets/gifs/arch-demo.gif\n  - type: static\n    source: diagrams/system-overview.gif\n    output: diagrams/system-overview.gif\ncombine:\n  output: assets/gifs/architecture-full.gif\n  layout: sequential\n```\n\nFile v1.9.16:modules/markdown-generation.md\n\n# Markdown Generation Module\n\nGenerate dual-tone markdown documentation from tape file annotations and manifest metadata.\n\n## Dual-Tone System\n\nTutorials are generated in two tones for different audiences:\n\n| Tone | Location | Audience | Style |\n|------|----------|----------|-------|\n| **Project Docs** | `docs/tutorials/` | Users getting started | Concise, action-oriented |\n| **Technical Book** | `book/src/tutorials/` | Developers learning deeply | Detailed, educational |\n\n## Annotation Sources\n\nContent comes from tape file annotations:\n\n```tape\n# @step Install the CLI\n# @docs-brief Install via cargo with a single command\n# @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.\nType \"cargo install skrills\"\n```\n\n- `@docs-brief` - Used for project docs (brief, focused)\n- `@book-detail` - Used for technical book (extended, contextual)\n- If only one is present, use it for both\n- If neither is present, generate minimal text from step name\n\n## Project Docs Format\n\n### Template Structure\n\n```markdown\n# {Tutorial Title}\n\n{Description from @description}\n\n![Demo]({relative-path-to-gif})\n\n## Prerequisites\n\n- Prerequisite 1\n- Prerequisite 2\n\n## Steps\n\n### {Step 1 Name}\n\n{@docs-brief content}\n\n```bash\n{command from Type directive}\n```\n\n### {Step 2 Name}\n\n{@docs-brief content}\n\n```bash\n{command}\n```\n\n## Next Steps\n\n- Link to related tutorial 1\n- Link to related tutorial 2\n```\n\n### Example Output\n\n```markdown\n# Quickstart\n\nInstall, validate, analyze, and serve in under a minute.\n\n![Quickstart demo](../../assets/gifs/quickstart.gif)\n\n## Prerequisites\n\n- Rust toolchain installed (`rustup`)\n- Terminal with UTF-8 support\n\n## Steps\n\n### Install skrills\n\nInstall via cargo with a single command.\n\n```bash\ncargo install skrills\n```\n\n### Validate Skills\n\nValidate and auto-fix missing frontmatter.\n\n```bash\nskrills validate --target codex --autofix\n```\n\n### Analyze Token Usage\n\nAnalyze skills for token optimization opportunities.\n\n```bash\nskrills analyze --min-tokens 500 --suggestions\n```\n\n### Start MCP Server\n\nStart the MCP server.\n\n```bash\nskrills serve\n```\n\n## Next Steps\n\n- [Sync Workflow](./sync.md) - Bidirectional sync between Claude Code and Codex CLI\n- [MCP Integration](./mcp.md) - Use skrills as an MCP server\n```\n\n## Technical Book Format\n\n### Template Structure\n\n```markdown\n# {Tutorial Title}\n\n{Extended description}\n\n## Overview\n\n{Context and learning objectives}\n\n![Demo]({relative-path-to-gif})\n\n## {Step 1 Name}\n\n{@book-detail content - multiple paragraphs allowed}\n\n```bash\n{command from Type directive}\n```\n\n{Additional explanation of what the command does}\n\n## {Step 2 Name}\n\n{@book-detail content}\n\n```bash\n{command}\n```\n\n{Explanation of output and next steps}\n\n## Summary\n\n{Key takeaways}\n\n## Further Reading\n\n- Internal link 1\n- External reference 1\n```\n\n### Example Output\n\n```markdown\n# Quickstart\n\nThis guide walks through the complete skrills workflow: installation, validation, analysis, and serving skills via MCP.\n\n## Overview\n\nWhat this guide covers:\n\n- How to install skrills using cargo\n- The difference between Claude Code and Codex CLI validation targets\n- How to analyze skills for token optimization\n- How to expose skills via the MCP protocol\n\n![Quickstart demo](../assets/gifs/quickstart.gif)\n\n## Install skrills\n\nThe 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.\n\n```bash\ncargo install skrills\n```\n\nThe binary will be placed in `~/.cargo/bin/`, which should be in your PATH if you installed Rust using rustup.\n\n## Validate Skills\n\nSkrills 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.\n\n```bash\nskrills validate --target codex --autofix\n```\n\nThe `--autofix` flag automatically derives missing frontmatter from the file path and content:\n1. Parses the skill filename to derive `name`\n2. Extracts the first paragraph as `description`\n3. Inserts YAML frontmatter at the file start\n\nThis makes migration from Claude Code to Codex straightforward.\n\n## Analyze Token Usage\n\nThe analyzer reports token counts for each skill and suggests optimizations for large skills that may consume excessive context.\n\n```bash\nskrills analyze --min-tokens 500 --suggestions\n```\n\nSkills exceeding the threshold are flagged with specific recommendations:\n- Split into multiple focused skills\n- Extract reusable modules\n- Remove redundant content\n\n## Start MCP Server\n\nWhen running as an MCP server, skrills exposes tools for skill discovery, validation, and analysis to any MCP-compatible client.\n\n```bash\nskrills serve\n```\n\nThe server listens on the default MCP port and responds to tool invocations from Claude Code or other clients.\n\n## Summary\n\n- Install skrills with `cargo install skrills`\n- Validate skills for Codex compatibility with `--target codex`\n- Use `--autofix` to automatically add required frontmatter\n- Analyze token usage to optimize context consumption\n- Serve skills via MCP for integration with AI assistants\n\n## Further Reading\n\n- [Sync Workflow](./sync.md) - Bidirectional synchronization between skill repositories\n- [MCP Integration](./mcp.md) - Advanced MCP server configuration\n- [Skill Debugging](./skill-debug.md) - Troubleshooting skill loading issues\n```\n\n## GIF Embedding\n\n### Relative Path Calculation\n\nGIFs are embedded with paths relative to the markdown file:\n\n| Markdown Location | GIF Location | Relative Path |\n|-------------------|--------------|---------------|\n| `docs/tutorials/quickstart.md` | `assets/gifs/quickstart.gif` | `../../assets/gifs/quickstart.gif` |\n| `book/src/tutorials/quickstart.md` | `assets/gifs/quickstart.gif` | `../../../assets/gifs/quickstart.gif` |\n| `README.md` | `assets/gifs/quickstart.gif` | `assets/gifs/quickstart.gif` |\n\n### Path Calculation Algorithm\n\n```python\ndef relative_gif_path(markdown_path: str, gif_path: str) -> str:\n    \"\"\"Calculate relative path from markdown file to GIF.\"\"\"\n    from pathlib import Path\n\n    md = Path(markdown_path)\n    gif = Path(gif_path)\n\n    # Get common ancestor\n    common = Path(*os.path.commonprefix([md.parts, gif.parts]))\n\n    # Calculate relative path\n    md_depth = len(md.parent.relative_to(common).parts)\n    gif_relative = gif.relative_to(common)\n\n    return \"../\" * md_depth + str(gif_relative)\n```\n\n### Embedding Format\n\n```markdown\n![{Alt text}]({relative-path})\n```\n\nFor multi-component tutorials with combined GIF:\n\n```markdown\n![{Tutorial title} demo]({relative-path-to-combined-gif})\n\n*This demo shows both terminal and browser interactions.*\n```\n\n## README Integration\n\n### Demo Section Template\n\n```markdown\n## Demos\n\n### {Tutorial 1 Title}\n![{Tutorial 1 title} demo]({gif-path})\n*{Description}. [Full tutorial]({docs-tutorial-path})*\n\n### {Tutorial 2 Title}\n![{Tutorial 2 title} demo]({gif-path})\n*{Description}. [Full tutorial]({docs-tutorial-path})*\n```\n\n### Generating README Section\n\n```python\ndef generate_readme_demos(tutorials: list) -> str:\n    \"\"\"Generate demo section for README.\"\"\"\n    lines = [\"## Demos\", \"\"]\n\n    for tutorial in tutorials:\n        lines.extend([\n            f\"### {tutorial['title']}\",\n            f\"![{tutorial['title']} demo]({tutorial['gif_path']})\",\n            f\"*{tutorial['description']}. [Full tutorial]({tutorial['docs_path']})*\",\n            \"\"\n        ])\n\n    return \"\\n\".join(lines)\n```\n\n### Updating README\n\nReplace the existing demo section or append if not present:\n\n```bash\n# Check if demo section exists\nif grep -q \"^## Demos\" README.md; then\n  # Replace section (between ## Demos and next ##)\n  sed -i '/^## Demos/,/^## [^D]/{ /^## [^D]/!d }' README.md\n  # Insert new content after ## Demos\nfi\n```\n\n## Book SUMMARY.md Integration\n\n### Template\n\n```markdown\n- [Tutorials](./tutorials/README.md)\n  - [{Tutorial 1 Title}](./tutorials/{name1}.md)\n  - [{Tutorial 2 Title}](./tutorials/{name2}.md)\n```\n\n### Detection and Update\n\n```bash\n# Check if tutorials section exists in SUMMARY.md\nif [[ -f \"book/src/SUMMARY.md\" ]]; then\n  if grep -q \"Tutorials\" book/src/SUMMARY.md; then\n    echo \"Tutorials section exists - update entries\"\n  else\n    echo \"Add Tutorials section to SUMMARY.md\"\n  fi\nfi\n```\n\n## Content Guidelines\n\n### Project Docs Style\n\n- Action-oriented imperatives: \"Install\", \"Run\", \"Configure\"\n- One paragraph per step maximum\n- Focus on commands and results\n- Minimal explanation of \"why\"\n- Include prerequisites section\n\n### Technical Book Style\n\n- Educational tone: \"This guide explains...\"\n- Multiple paragraphs allowed per step\n- Explain rationale and context\n- Include troubleshooting tips\n- Reference related concepts\n- Add summary and further reading sections\n\n### Common Rules (Both)\n\n- No filler phrases (\"simply\", \"just\", \"easily\")\n- No emojis or decorative elements\n- Grounded, specific language\n- Code blocks for all commands\n- Consistent heading hierarchy\n- Prose text wraps at 80 chars per line (hybrid wrapping:\n  prefer sentence/clause boundaries over arbitrary breaks)\n- Blank line before and after every heading\n- ATX headings onl\n\nArchive v1.9.14: 6 files, 18357 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10011b), modules/tape-validation.md (9405b), skill-card.md (2616b), SKILL.md (20046b), _meta.json (147b)\n\nArchive v1.9.13: 6 files, 18187 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10011b), modules/tape-validation.md (9405b), skill-card.md (2215b), SKILL.md (20046b), _meta.json (147b)\n\nArchive v1.9.12: 6 files, 18273 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10011b), modules/tape-validation.md (9405b), skill-card.md (2304b), SKILL.md (20046b), _meta.json (147b)\n\nArchive v1.0.3: 6 files, 18318 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10011b), modules/tape-validation.md (9405b), skill-card.md (2504b), SKILL.md (20046b), _meta.json (146b)\n\nArchive v1.0.2: 6 files, 18555 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10034b), modules/tape-validation.md (9405b), skill-card.md (2807b), SKILL.md (20261b), _meta.json (146b)\n\nArchive v1.0.1: 5 files, 17163 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10034b), modules/tape-validation.md (9405b), SKILL.md (20261b), _meta.json (146b)\n\nArchive v1.0.0: 5 files, 17163 bytes\n\nFiles: modules/manifest-parsing.md (8831b), modules/markdown-generation.md (10034b), modules/tape-validation.md (9405b), SKILL.md (20261b), _meta.json (146b)","readmeExcerpt":"Skill: tutorial-updates Owner: athola Summary: Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording Tags: latest:1.9.19 Version history: v1.9.19 | 2026-08-26T13:21:17.158Z | user Release v1.9.19 v1.9.17 | 2026-07-30T05:41:17.600Z | user Release v1.9.17 v1.9.16 | 2026-07-14T19:58:05.764Z | user Release v1.9.16 v1.9.14 | 2026-06-30T18:05:52.000Z | user Release v1.","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"/update-tutorial quickstart        # Single tutorial by name\n/update-tutorial sync mcp          # Multiple tutorials\n/update-tutorial --all             # All tutorials with manifests\n/update-tutorial --list            # Show available tutorials\n/update-tutorial --scaffold        # Create structure without recording"},{"language":"text","snippet":"**Verification:** Run the command with `--help` flag to verify availability.\n- tutorial-updates:discovery\n- tutorial-updates:validation\n- tutorial-updates:rebuild\n- tutorial-updates:recording\n- tutorial-updates:generation\n- tutorial-updates:integration"},{"language":"bash","snippet":"# Find manifest files\nfind . -name \"*.manifest.yaml\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n\n# Find tape files\nfind . -name \"*.tape\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20\n\n# Find browser specs\nfind . -name \"*.spec.ts\" -path \"*/browser/*\" -type f \\\n  -not -path \"*/.venv/*\" -not -path \"*/__pycache__/*\" \\\n  -not -path \"*/node_modules/*\" -not -path \"*/.git/*\" \\\n  2>/dev/null | head -20"},{"language":"text","snippet":"**Verification:** Run the command with `--help` flag to verify availability.\nAvailable tutorials:\n  quickstart     assets/tapes/quickstart.tape\n  sync           assets/tapes/sync.tape (manifest)\n  mcp            assets/tapes/mcp.manifest.yaml (terminal + browser)\n  skill-debug    assets/tapes/skill-debug.tape"},{"language":"bash","snippet":"# Required: Output directive exists\ngrep -q '^Output ' \"$tape_file\" || echo \"ERROR: Missing Output directive\"\n\n# Check for balanced quotes in Type directives\ngrep '^Type ' \"$tape_file\" | while read -r line; do\n  quote_count=$(echo \"$line\" | tr -cd '\"' | wc -c)\n  if [ $((quote_count % 2)) -ne 0 ]; then\n    echo \"ERROR: Unbalanced quotes: $line\"\n  fi\ndone"},{"language":"bash","snippet":"# Extract commands from Type directives\ngrep '^Type ' \"$tape_file\" | sed 's/^Type \"//' | sed 's/\"$//' | while read -r cmd; do\n  # Skip comments, clear, and echo commands\n  [[ \"$cmd\" =~ ^# ]] && continue\n  [[ \"$cmd\" == \"clear\" ]] && continue\n\n  # For skrills commands, validate flags exist\n  if [[ \"$cmd\" =~ ^skrills ]]; then\n    base_cmd=$(echo \"$cmd\" | awk '{print $1, $2}')\n    flags=$(echo \"$cmd\" | grep -oE '\\-\\-[a-zA-Z0-9-]+' || true)\n\n    for flag in $flags; do\n      if ! $base_cmd --help 2>&1 | grep -q -- \"$flag\"; then\n        echo \"ERROR: Invalid flag '$flag' in command: $cmd\"\n      fi\n    done\n  fi\ndone"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: tutorial-updates\ndescription: |\n  Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording\nversion: 1.9.8\ntriggers:\n  - tutorial\n  - gif\n  - vhs\n  - playwright\n  - documentation\n  - demo\n  - tutorial assets need refreshing\nmetadata: {\"openclaw\": {\"homepage\": \"https://github.com/athola/claude-night-market/tree/master/plugins/sanctum\", \"emoji\": \"\\ud83e\\udd9e\", \"requires\": {\"config\": [\"night-market.sanctum:shared\", \"night-market.sanctum:git-workspace-review\", \"night-market.scry:vhs-recording\", \"night-market.scry:browser-recording\", \"night-market.scry:gif-generation\", \"night-market.scry:media-composition\"]}}}\nsource: claude-night-market\nsource_plugin: sanctum\n---\n\n> **Night Market Skill** — ported from [claude-night-market/sanctum](https://github.com/athola/claude-night-market/tree/master/plugins/sanctum). For the full experience with agents, hooks, and commands, install the Claude Code plugin.\n\n\n## Table of Contents\n\n- [Overview](#overview)\n- [Command Options](#command-options)\n- [Required TodoWrite Items](#required-todowrite-items)\n- [Phase 1: Discovery (`tutorial-updates:discovery`)](#phase-1:-discovery-(tutorial-updates:discovery))\n- [Step 1.1: Locate Tutorial Assets](#step-11:-locate-tutorial-assets)\n- [Step 1.2: Parse Manifests](#step-12:-parse-manifests)\n- [Step 1.3: Handle Options](#step-13:-handle-options)\n- [Phase 1.5: Validation (`tutorial-updates:validation`)](#phase-15:-validation-(tutorial-updates:validation))\n- [Step 1.5.1: VHS Syntax Validation](#step-151:-vhs-syntax-validation)\n- [Step 1.5.2: Extract and Validate CLI Commands](#step-152:-extract-and-validate-cli-commands)\n- [Step 1.5.3: Verify Demo Data Exists](#step-153:-verify-demo-data-exists)\n- [Step 1.5.4: Test Commands Locally](#step-154:-test-commands-locally)\n- [Validation Flags](#validation-flags)\n- [Validation Exit Criteria](#validation-exit-criteria)\n- [Phase 1.6: Binary Rebuild (`tutorial-updates:rebuild`)](#phase-16:-binary-rebuild-(tutorial-updates:rebuild))\n- [Step 1.6.1: Detect Build System](#step-161:-detect-build-system)\n- [Step 1.6.2: Check Binary Freshness](#step-162:-check-binary-freshness)\n- [Step 1.6.3: Rebuild Binary](#step-163:-rebuild-binary)\n- [Step 1.6.4: Verify Binary Accessibility](#step-164:-verify-binary-accessibility)\n- [Rebuild Flags](#rebuild-flags)\n- [Rebuild Exit Criteria](#rebuild-exit-criteria)\n- [Phase 2: Recording (`tutorial-updates:recording`)](#phase-2:-recording-(tutorial-updates:recording))\n- [Step 2.1: Process Tape Components](#step-21:-process-tape-components)\n- [Step 2.2: Process Browser Components](#step-22:-process-browser-components)\n- [Step 2.3: Handle Multi-Component Tutorials](#step-23:-handle-multi-component-tutorials)\n- [Phase 3: Generation (`tutorial-updates:generation`)](#phase-3:-generation-(tutorial-updates:generation))\n- [Step 3.1: Parse Tape Annotations](#step-31:-parse-tape-annotations)\n- [Step 3.2: Generate Dual-Tone Markdown](#step-32:-generate-dual-tone-mark"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7d107jg9jv602h9ytsegydq184a42s\",\n  \"slug\": \"nm-sanctum-tutorial-updates\",\n  \"version\": \"1.9.19\",\n  \"publishedAt\": 1787750477158\n}"},{"path":"modules/manifest-parsing.md","content":"# Manifest Parsing Module\n\nParse `.manifest.yaml` files and tape file annotations for tutorial orchestration.\n\n## Manifest Schema\n\nTutorial manifests define multi-component tutorials with composition rules:\n\n```yaml\n# Full manifest schema\nname: string              # Required: identifier for the tutorial\ntitle: string             # Optional: human-readable title\ndescription: string       # Optional: brief description\n\ncomponents:               # Required: list of media components\n  - type: tape            # Component type: tape, playwright, static\n    source: path/to.tape  # Path to source file (relative to manifest)\n    output: path/to.gif   # Path for generated output\n    options:              # Optional: component-specific options\n      fps: 10\n      width: 800\n\n  - type: playwright\n    source: browser/spec.ts\n    output: assets/gifs/browser.gif\n    requires:             # Optional: commands to run before\n      - \"npm run serve\"\n\n  - type: static          # Pre-existing asset (no generation)\n    source: existing.gif\n    output: existing.gif\n\ncombine:                  # Optional: composition rules\n  output: combined.gif    # Path for combined output\n  layout: vertical        # Layout: vertical, horizontal, sequential, grid, pip\n  options:\n    padding: 10\n    background: \"#1a1a2e\"\n```\n\n## Component Types\n\n### Tape Components\n\nVHS tape files for terminal recordings:\n\n```yaml\n- type: tape\n  source: quickstart.tape\n  output: assets/gifs/quickstart.gif\n  options:\n    # Override tape file settings if needed\n    width: 1000\n    height: 500\n```\n\n### Playwright Components\n\nBrowser automation specs:\n\n```yaml\n- type: playwright\n  source: browser/mcp-dashboard.spec.ts\n  output: assets/gifs/mcp-browser.gif\n  requires:\n    - \"skrills serve\"\n  options:\n    fps: 12\n    width: 1280\n```\n\nThe `requires` array specifies commands to run before the spec. These run as background processes and are terminated after recording.\n\n### Static Components\n\nPre-existing assets that don't need generation:\n\n```yaml\n- type: static\n  source: diagrams/architecture.gif\n  output: diagrams/architecture.gif\n```\n\n## Parsing Tape File Annotations\n\nTape files contain inline annotations for documentation generation:\n\n### Annotation Format\n\n```tape\n# @title: Tutorial Title\n# @description: Brief description for README\n\n# @step Step Name\n# @docs-brief Concise text for project docs\n# @book-detail Extended explanation for technical book\nType \"command\"\nEnter\n```\n\n### Annotation Types\n\n| Annotation | Scope | Purpose |\n|------------|-------|---------|\n| `@title` | File | Tutorial title |\n| `@description` | File | Brief description |\n| `@step` | Block | Step heading |\n| `@docs-brief` | Block | Concise docs text |\n| `@book-detail` | Block | Extended book text |\n\n### Parsing Algorithm\n\n```python\ndef parse_tape_annotations(tape_content: str) -> dict:\n    \"\"\"Parse tape file for documentation annotations.\"\"\"\n    result = {\n        \"title\": None,\n        \"description\": None,\n        \"steps\": []\n    }\n\n   "},{"path":"modules/markdown-generation.md","content":"# Markdown Generation Module\n\nGenerate dual-tone markdown documentation from tape file annotations and manifest metadata.\n\n## Dual-Tone System\n\nTutorials are generated in two tones for different audiences:\n\n| Tone | Location | Audience | Style |\n|------|----------|----------|-------|\n| **Project Docs** | `docs/tutorials/` | Users getting started | Concise, action-oriented |\n| **Technical Book** | `book/src/tutorials/` | Developers learning deeply | Detailed, educational |\n\n## Annotation Sources\n\nContent comes from tape file annotations:\n\n```tape\n# @step Install the CLI\n# @docs-brief Install via cargo with a single command\n# @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.\nType \"cargo install skrills\"\n```\n\n- `@docs-brief` - Used for project docs (brief, focused)\n- `@book-detail` - Used for technical book (extended, contextual)\n- If only one is present, use it for both\n- If neither is present, generate minimal text from step name\n\n## Project Docs Format\n\n### Template Structure\n\n```markdown\n# {Tutorial Title}\n\n{Description from @description}\n\n![Demo]({relative-path-to-gif})\n\n## Prerequisites\n\n- Prerequisite 1\n- Prerequisite 2\n\n## Steps\n\n### {Step 1 Name}\n\n{@docs-brief content}\n\n```bash\n{command from Type directive}\n```\n\n### {Step 2 Name}\n\n{@docs-brief content}\n\n```bash\n{command}\n```\n\n## Next Steps\n\n- Link to related tutorial 1\n- Link to related tutorial 2\n```\n\n### Example Output\n\n```markdown\n# Quickstart\n\nInstall, validate, analyze, and serve in under a minute.\n\n![Quickstart demo](../../assets/gifs/quickstart.gif)\n\n## Prerequisites\n\n- Rust toolchain installed (`rustup`)\n- Terminal with UTF-8 support\n\n## Steps\n\n### Install skrills\n\nInstall via cargo with a single command.\n\n```bash\ncargo install skrills\n```\n\n### Validate Skills\n\nValidate and auto-fix missing frontmatter.\n\n```bash\nskrills validate --target codex --autofix\n```\n\n### Analyze Token Usage\n\nAnalyze skills for token optimization opportunities.\n\n```bash\nskrills analyze --min-tokens 500 --suggestions\n```\n\n### Start MCP Server\n\nStart the MCP server.\n\n```bash\nskrills serve\n```\n\n## Next Steps\n\n- [Sync Workflow](./sync.md) - Bidirectional sync between Claude Code and Codex CLI\n- [MCP Integration](./mcp.md) - Use skrills as an MCP server\n```\n\n## Technical Book Format\n\n### Template Structure\n\n```markdown\n# {Tutorial Title}\n\n{Extended description}\n\n## Overview\n\n{Context and learning objectives}\n\n![Demo]({relative-path-to-gif})\n\n## {Step 1 Name}\n\n{@book-detail content - multiple paragraphs allowed}\n\n```bash\n{command from Type directive}\n```\n\n{Additional explanation of what the command does}\n\n## {Step 2 Name}\n\n{@book-detail content}\n\n```bash\n{command}\n```\n\n{Explanation of output and next steps}\n\n## Summary\n\n{Key takeaways}\n\n## Further Reading\n\n- Internal link 1\n- External reference 1\n```\n\n### Example O"},{"path":"modules/tape-validation.md","content":"# Tape Validation Module\n\nPre-flight validation for VHS tape files before GIF generation. Validate commands work correctly and demo data exists BEFORE running the time-consuming VHS recording.\n\n## Overview\n\nThis module catches errors early:\n- Stale binaries detected before recording (Phase 0)\n- Invalid CLI flags discovered before GIF generation\n- Missing demo data detected before recording starts\n- VHS syntax issues reported before execution\n\n## Validation Phases\n\n### Phase 0: Binary Freshness Check\n\n**CRITICAL**: Verify the CLI binary matches the latest source code. Stale binaries produce misleading demos.\n\n```bash\n# Check if binary is older than Git HEAD\ncheck_binary_freshness() {\n  local binary_name=\"$1\"\n\n  # Locate binary in PATH\n  local binary_path=$(which \"$binary_name\" 2>/dev/null)\n\n  if [ -z \"$binary_path\" ]; then\n    echo \"WARNING: Binary '$binary_name' not found in PATH\"\n    return 1\n  fi\n\n  # Get binary modification time (Linux/macOS compatible)\n  local binary_mtime\n  binary_mtime=$(stat -c %Y \"$binary_path\" 2>/dev/null || \\\n                 stat -f %m \"$binary_path\" 2>/dev/null)\n\n  # Get Git HEAD commit time\n  local git_head_time=$(git log -1 --format=%ct 2>/dev/null)\n\n  if [ -z \"$git_head_time\" ]; then\n    echo \"WARNING: Not a git repository, skipping freshness check\"\n    return 2\n  fi\n\n  # Compare timestamps\n  if [ \"$binary_mtime\" -lt \"$git_head_time\" ]; then\n    echo \"STALE: Binary is older than Git HEAD\"\n    echo \"  Binary: $(date -d @$binary_mtime 2>/dev/null || date -r $binary_mtime)\"\n    echo \"  HEAD:   $(date -d @$git_head_time 2>/dev/null || date -r $git_head_time)\"\n    echo \"\"\n    echo \"RECOMMENDATION: Rebuild binary before recording tape\"\n    echo \"  cargo install --path crates/cli --locked\"\n    return 1\n  else\n    echo \"OK: Binary is up-to-date with Git HEAD\"\n    return 0\n  fi\n}\n```\n\n**Error handling**:\n- Exit code 0: Binary is fresh\n- Exit code 1: Binary is stale (older than HEAD)\n- Exit code 2: Cannot determine (not in git repo or binary not found)\n\n**Integration**:\n```bash\n# Run before tape validation\ncheck_binary_freshness \"skrills\"\nif [ $? -eq 1 ]; then\n  echo \"ERROR: Stale binary detected\"\n  echo \"Run 'cargo install --path crates/cli --locked' to rebuild\"\n  exit 1\nfi\n```\n\n## Command Validation Phases\n\n### 1. VHS Syntax Validation\n\nCheck that the tape file has valid VHS syntax:\n\n```bash\n# Required: Output directive\ngrep -q '^Output ' \"$tape_file\" || echo \"ERROR: Missing Output directive\"\n\n# Check for balanced quotes in Type directives\ngrep '^Type ' \"$tape_file\" | while read -r line; do\n  # Count quotes (should be even)\n  quote_count=$(echo \"$line\" | tr -cd '\"' | wc -c)\n  if [ $((quote_count % 2)) -ne 0 ]; then\n    echo \"ERROR: Unbalanced quotes in: $line\"\n  fi\ndone\n```\n\n**Error examples**:\n- \"Missing Output directive in quickstart.tape:1\"\n- \"Unbalanced quotes in Type directive at quickstart.tape:15\"\n\n### 2. Command Extraction\n\nParse `Type` directives to extract shell commands:\n\n```bash\n# Extract commands from Type dir"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording Skill: tutorial-updates Owner: athola Summary: Generates or updates tutorials from VHS tapes and Playwright specs with dual-tone markdown and GIF recording Tags: latest:1.9.19 Version history: v1.9.19 | 2026-08-26T13:21:17.158Z | user Release v1.9.19 v1.9.17 | 2026-07-30T05:41:17.600Z | user Release v1.9.17 v1.9.16 | 2026-07-14T19:58:05.764Z | user Release v1.9.16 v1.9.14 | 2026-06-30T18:05:52.000Z | user Release v1.","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1009,"uniquenessScore":48,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T08:08:54.633Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T08:08:54.633Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T10:43:56.633Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}