| 1 | #!/usr/bin/env bash |
| 2 | # Record the v0.9.2 real-session media (#4906) into a sealed environment. |
| 3 | # |
| 4 | # This wrapper exists for one reason: a recording is published footage, and the |
| 5 | # failure mode is not "the take is bad" — it is "the take contains your home |
| 6 | # directory, your shell history, or a credential". Every guard below refuses to |
| 7 | # record rather than producing something that has to be scrubbed afterwards. |
| 8 | # |
| 9 | # It records a REAL session. It does not stage or reconstruct output. If the |
| 10 | # take is unimpressive, change the task and re-run; do not edit frames. |
| 11 | # |
| 12 | # Procedure and acceptance checklist: docs/releases/v0.9.2-media-plan.md |
| 13 | # Verify the result with: scripts/media/check-media-assets.py |
| 14 | set -euo pipefail |
| 15 | |
| 16 | repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" |
| 17 | tape="${repo_root}/docs/evidence/v092-first-fleet-session.tape" |
| 18 | demo_dir="${CW_MEDIA_DEMO_DIR:-/tmp/cw-media-demo}" |
| 19 | out_dir="${CW_MEDIA_OUT_DIR:-${repo_root}/.media-out}" |
| 20 | |
| 21 | die() { echo "ERROR: $*" >&2; exit 1; } |
| 22 | |
| 23 | # --- Preconditions ---------------------------------------------------------- |
| 24 | |
| 25 | command -v vhs >/dev/null || die "vhs is not installed (brew install vhs)" |
| 26 | command -v ffmpeg >/dev/null || die "ffmpeg is not installed (brew install ffmpeg)" |
| 27 | [[ -f "${tape}" ]] || die "missing tape: ${tape}" |
| 28 | |
| 29 | # Record the exact binary that was dogfooded, not whatever is first on PATH. |
| 30 | binary="$(command -v codewhale || true)" |
| 31 | [[ -n "${binary}" ]] || die "codewhale is not on PATH — run scripts/release/install-dogfood.sh first" |
| 32 | |
| 33 | version_line="$("${binary}" --version)" |
| 34 | head_sha="$(git -C "${repo_root}" rev-parse HEAD)" |
| 35 | short_sha="${head_sha:0:12}" |
| 36 | if [[ "${version_line}" != *"${short_sha}"* ]]; then |
| 37 | die "installed codewhale is not this checkout. |
| 38 | installed: ${version_line} |
| 39 | HEAD: ${short_sha} |
| 40 | Recording a SHA that is not the release candidate makes the asset a lie about |
| 41 | what the product looks like. Run scripts/release/install-dogfood.sh." |
| 42 | fi |
| 43 | |
| 44 | if [[ -n "$(git -C "${repo_root}" status --porcelain --untracked-files=no)" ]]; then |
| 45 | die "refusing to record from a dirty tree — the capture receipt could not name an exact source" |
| 46 | fi |
| 47 | |
| 48 | # --- Credential guard ------------------------------------------------------- |
| 49 | # The session must be keyless on a sealed local route. Anything that looks like |
| 50 | # a provider credential in the environment could reach the screen. |
| 51 | leaked=() |
| 52 | while IFS='=' read -r name _; do |
| 53 | case "${name}" in |
| 54 | *API_KEY|*_TOKEN|*_SECRET|ANTHROPIC_*|OPENAI_*|DEEPSEEK_*|MOONSHOT_*|KIMI_*|GROQ_*|XAI_*|GEMINI_*) |
| 55 | leaked+=("${name}") ;; |
| 56 | esac |
| 57 | done < <(env) |
| 58 | if (( ${#leaked[@]} )); then |
| 59 | die "provider credentials are present in the environment: ${leaked[*]} |
| 60 | Re-run from a clean shell (\`env -i\` or a fresh terminal). The recording must |
| 61 | be demonstrably keyless." |
| 62 | fi |
| 63 | |
| 64 | # --- Sealed environment ----------------------------------------------------- |
| 65 | sealed="$(mktemp -d)" |
| 66 | mkdir -p "${sealed}/.codewhale" "${demo_dir}" "${out_dir}" |
| 67 | cleanup() { rm -rf "${sealed}"; } |
| 68 | trap cleanup EXIT |
| 69 | |
| 70 | # Blue Stage dark, selected in the product rather than imitated by the |
| 71 | # emulator palette (see the tape's note). |
| 72 | cat > "${sealed}/.codewhale/config.toml" <<'TOML' |
| 73 | theme = "Blue Stage" |
| 74 | TOML |
| 75 | |
| 76 | echo "Recording ${version_line}" |
| 77 | echo " tape: ${tape}" |
| 78 | echo " workspace: ${demo_dir}" |
| 79 | echo " sealed: HOME=${sealed}" |
| 80 | echo |
| 81 | |
| 82 | pushd "${demo_dir}" >/dev/null |
| 83 | HOME="${sealed}" \ |
| 84 | CODEWHALE_HOME="${sealed}/.codewhale" \ |
| 85 | CODEWHALE_CONFIG_PATH="${sealed}/.codewhale/config.toml" \ |
| 86 | CODEWHALE_MCP_CONFIG="${sealed}/.codewhale/mcp.json" \ |
| 87 | vhs "${tape}" |
| 88 | popd >/dev/null |
| 89 | |
| 90 | gif="${demo_dir}/first-fleet-session.gif" |
| 91 | [[ -f "${gif}" ]] || die "vhs produced no GIF at ${gif}" |
| 92 | |
| 93 | # --- Derivatives (media plan step 4) ---------------------------------------- |
| 94 | mv "${gif}" "${out_dir}/first-fleet-session.gif" |
| 95 | gif="${out_dir}/first-fleet-session.gif" |
| 96 | |
| 97 | ffmpeg -loglevel error -y -i "${gif}" -movflags +faststart -pix_fmt yuv420p \ |
| 98 | -vf "scale=1280:720" -an "${out_dir}/first-fleet-session.mp4" |
| 99 | ffmpeg -loglevel error -y -i "${gif}" -frames:v 1 "${out_dir}/first-fleet-session.png" |
| 100 | |
| 101 | # --- Capture receipt -------------------------------------------------------- |
| 102 | # So the asset can be re-shot when the UI changes, instead of quietly aging |
| 103 | # into a lie about what the product looks like. |
| 104 | cat > "${out_dir}/capture.json" <<JSON |
| 105 | { |
| 106 | "id": "first-fleet-session", |
| 107 | "issue": "https://github.com/Hmbown/CodeWhale/issues/4906", |
| 108 | "recorded_from_commit": "${head_sha}", |
| 109 | "version_line": "${version_line}", |
| 110 | "tape": "docs/evidence/v092-first-fleet-session.tape", |
| 111 | "grid": "120x32", |
| 112 | "product_theme": "Blue Stage", |
| 113 | "route": "sealed loopback Ollama; no hosted provider, no account, no credential", |
| 114 | "workspace": "${demo_dir}", |
| 115 | "sha256": { |
| 116 | "gif": "$(shasum -a 256 "${out_dir}/first-fleet-session.gif" | cut -d' ' -f1)", |
| 117 | "mp4": "$(shasum -a 256 "${out_dir}/first-fleet-session.mp4" | cut -d' ' -f1)", |
| 118 | "png": "$(shasum -a 256 "${out_dir}/first-fleet-session.png" | cut -d' ' -f1)" |
| 119 | } |
| 120 | } |
| 121 | JSON |
| 122 | |
| 123 | echo |
| 124 | echo "Wrote:" |
| 125 | ls -la "${out_dir}" |
| 126 | echo |
| 127 | echo "Next:" |
| 128 | echo " 1. WATCH THE TAKE. If it is not worth showing, change the task and re-run." |
| 129 | echo " 2. python3 scripts/media/check-media-assets.py --dir ${out_dir}" |
| 130 | echo " 3. Follow the acceptance checklist in docs/releases/v0.9.2-media-plan.md" |
| 131 | echo " before copying anything into web/public/media/ or flipping the" |
| 132 | echo " manifest entry to published." |
| 133 |